From e87e998f18c665b5d72a4af48174aa977c37e6f6 Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Thu, 22 Jan 2026 16:00:24 +0100 Subject: [PATCH 01/14] Add RESTful endpoints for thoughts collection and single thought --- data.json | 12 ++++++------ package.json | 25 ++++++++++++++++++------- server.js | 44 ++++++++++++++++++++++++++++++++++---------- 3 files changed, 58 insertions(+), 23 deletions(-) diff --git a/data.json b/data.json index a2c844ff..11ff432b 100644 --- a/data.json +++ b/data.json @@ -1,5 +1,5 @@ [ - { + { "_id": "682bab8c12155b00101732ce", "message": "Berlin baby", "hearts": 37, @@ -7,7 +7,7 @@ "__v": 0 }, { - "_id": "682e53cc4fddf50010bbe739", + "_id": "682e53cc4fddf50010bbe739", "message": "My family!", "hearts": 0, "createdAt": "2025-05-22T22:29:32.232Z", @@ -25,7 +25,7 @@ "message": "Newly washed bedlinen, kids that sleeps through the night.. FINGERS CROSSED 🤞🏼\n", "hearts": 6, "createdAt": "2025-05-21T21:42:23.862Z", - "__v": 0 + "__v": 0 }, { "_id": "682e45804fddf50010bbe736", @@ -53,7 +53,7 @@ "message": "A god joke: \nWhy did the scarecrow win an award?\nBecause he was outstanding in his field!", "hearts": 12, "createdAt": "2025-05-20T20:54:51.082Z", - "__v": 0 + "__v": 0 }, { "_id": "682cebbe17487d0010a298b5", @@ -74,7 +74,7 @@ "message": "Summer is coming...", "hearts": 2, "createdAt": "2025-05-20T15:03:22.379Z", - "__v": 0 + "__v": 0 }, { "_id": "682c706c951f7a0017130024", @@ -118,4 +118,4 @@ "createdAt": "2025-05-19T22:07:08.999Z", "__v": 0 } -] \ No newline at end of file +] diff --git a/package.json b/package.json index bf25bb68..d79c6497 100644 --- a/package.json +++ b/package.json @@ -2,18 +2,29 @@ "name": "project-api", "version": "1.0.0", "description": "Project API", + "homepage": "https://github.com/SaraEnderborg/js-project-api#readme", + "bugs": { + "url": "https://github.com/SaraEnderborg/js-project-api/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/SaraEnderborg/js-project-api.git" + }, + "license": "ISC", + "author": "", + "type": "module", + "main": "server.js", "scripts": { "start": "babel-node server.js", "dev": "nodemon server.js --exec babel-node" }, - "author": "", - "license": "ISC", "dependencies": { - "@babel/core": "^7.17.9", - "@babel/node": "^7.16.8", - "@babel/preset-env": "^7.16.11", + "@babel/core": "^7.28.6", + "@babel/node": "^7.28.6", + "@babel/preset-env": "^7.28.6", "cors": "^2.8.5", - "express": "^4.17.3", - "nodemon": "^3.0.1" + "express": "^4.22.1", + "express-list-endpoints": "^7.1.1", + "nodemon": "^3.1.11" } } diff --git a/server.js b/server.js index f47771bd..7ed5ac1e 100644 --- a/server.js +++ b/server.js @@ -1,22 +1,46 @@ -import cors from "cors" -import express from "express" +import cors from "cors"; +import express from "express"; +import listEndpoints from "express-list-endpoints"; +import thoughtsData from "./data.json" with { type: "json" }; // 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 app = express() +const port = process.env.PORT || 8080; +const app = express(); // Add middlewares to enable cors and json body parsing -app.use(cors()) -app.use(express.json()) +app.use(cors()); +app.use(express.json()); // Start defining your routes here app.get("/", (req, res) => { - res.send("Hello Technigo!") -}) + const endpoints = listEndpoints(app); + + res.json({ + message: "Welcome to my Happy Thoughts API.", + endpoints: endpoints, + }); +}); + +app.get("/thoughts", (req, res) => { + res.json(thoughtsData); +}); + +app.get("/thoughts/:id", (req, res) => { + const { id } = req.params; + const thought = thoughtsData.find((thought) => thought._id === id); + + if (!thought) { + return res + .status(404) + .json({ error: `thought with id ${id} does not exist` }); + } + + res.json(thought); +}); // Start the server app.listen(port, () => { - console.log(`Server running on http://localhost:${port}`) -}) + console.log(`Server running on http://localhost:${port}`); +}); From 239ab3ae09456ec0642e0f5a5bccb0d7d2fcc368 Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Thu, 22 Jan 2026 16:15:59 +0100 Subject: [PATCH 02/14] Add filtering, sorting and limiting to thoughts endpoint --- server.js | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index 7ed5ac1e..33ebc760 100644 --- a/server.js +++ b/server.js @@ -24,7 +24,32 @@ app.get("/", (req, res) => { }); app.get("/thoughts", (req, res) => { - res.json(thoughtsData); + const { minHearts, search, limit, sort } = req.query; + let filteredThoughts = thoughtsData; + + if (minHearts) { + filteredThoughts = filteredThoughts.filter( + (thought) => thought.hearts >= Number(minHearts), + ); + } + + if (search) { + filteredThoughts = filteredThoughts.filter((thought) => + thought.message.toLowerCase().includes(search.toLowerCase()), + ); + } + + if (sort === "createdAt") { + filteredThoughts.sort( + (a, b) => new Date(b.createdAt) - new Date(a.createdAt), + ); + } + + if (limit) { + filteredThoughts = filteredThoughts.slice(0, Number(limit)); + } + + res.json(filteredThoughts); }); app.get("/thoughts/:id", (req, res) => { From 3e52d2b55db8a9b5a1ac00b50e29fd6f86848f3f Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Fri, 23 Jan 2026 13:00:26 +0100 Subject: [PATCH 03/14] added description comments --- server.js | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/server.js b/server.js index 33ebc760..28bbdf02 100644 --- a/server.js +++ b/server.js @@ -6,14 +6,18 @@ import thoughtsData from "./data.json" with { type: "json" }; // 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 = 8080; // This would break on most hosting platforms! const port = process.env.PORT || 8080; const app = express(); -// Add middlewares to enable cors and json body parsing app.use(cors()); app.use(express.json()); -// Start defining your routes here +// === ROUTES === + +// Root endpoint: returns API documentation +// Lists all available endpoints using express-list-endpoints + app.get("/", (req, res) => { const endpoints = listEndpoints(app); @@ -23,28 +27,41 @@ app.get("/", (req, res) => { }); }); +// Get all thoughts (with optional filtering and sorting) +// Query parameters: +// - minHearts: filter thoughts with at least this many hearts +// - search: filter thoughts containing this text (case-insensitive) +// - sort: if set to "createdAt", sorts by newest first +// - limit: limits the number of results returned + app.get("/thoughts", (req, res) => { const { minHearts, search, limit, sort } = req.query; + + // start with all thoughts then apply filters let filteredThoughts = thoughtsData; + // Filter by minimum hearts if (minHearts) { filteredThoughts = filteredThoughts.filter( (thought) => thought.hearts >= Number(minHearts), ); } + // Filter by search text ( case-insensitive) if (search) { filteredThoughts = filteredThoughts.filter((thought) => thought.message.toLowerCase().includes(search.toLowerCase()), ); } + // Sort by createdAt (ex. newest first) if (sort === "createdAt") { filteredThoughts.sort( (a, b) => new Date(b.createdAt) - new Date(a.createdAt), ); } + // Limit the number of results if (limit) { filteredThoughts = filteredThoughts.slice(0, Number(limit)); } @@ -52,20 +69,25 @@ app.get("/thoughts", (req, res) => { res.json(filteredThoughts); }); +// Get a single thought by id app.get("/thoughts/:id", (req, res) => { const { id } = req.params; + + // Find thought with matching id const thought = thoughtsData.find((thought) => thought._id === id); + // If not found, return 404 error if (!thought) { return res .status(404) .json({ error: `thought with id ${id} does not exist` }); } + // Return the found thought res.json(thought); }); -// Start the server +// Start the server and listen on the specified port app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); From cc89cd03ca429f3bc62a1eff2566880de5120ac9 Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Thu, 29 Jan 2026 14:27:18 +0100 Subject: [PATCH 04/14] setup up MongoDB Atlas with mongoose --- package.json | 3 +++ server.js | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/package.json b/package.json index d79c6497..ad1c94c3 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,11 @@ "@babel/node": "^7.28.6", "@babel/preset-env": "^7.28.6", "cors": "^2.8.5", + "dotenv": "^17.2.3", "express": "^4.22.1", "express-list-endpoints": "^7.1.1", + "mongodb": "^7.0.0", + "mongoose": "^9.1.5", "nodemon": "^3.1.11" } } diff --git a/server.js b/server.js index 28bbdf02..40b6f621 100644 --- a/server.js +++ b/server.js @@ -1,8 +1,13 @@ import cors from "cors"; import express from "express"; +import mongoose from "mongoose"; +import "dotenv/config"; import listEndpoints from "express-list-endpoints"; import thoughtsData from "./data.json" with { type: "json" }; +const mongoUrl = process.env.MONGODB_URI; +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 @@ -10,9 +15,12 @@ import thoughtsData from "./data.json" with { type: "json" }; const port = process.env.PORT || 8080; const app = express(); +//middlewares to enable cors and json body parsing app.use(cors()); app.use(express.json()); +// Mongoose schema + // === ROUTES === // Root endpoint: returns API documentation From a7d43417229fce53634584e474a07fd8e3fc689e Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Thu, 29 Jan 2026 16:14:01 +0100 Subject: [PATCH 05/14] Add MongoDB seeding with Mongoose and turn off RESET_DB --- server.js | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index 40b6f621..b77f000f 100644 --- a/server.js +++ b/server.js @@ -2,10 +2,12 @@ import cors from "cors"; import express from "express"; import mongoose from "mongoose"; import "dotenv/config"; +console.log("RESET_DB:", process.env.RESET_DB); import listEndpoints from "express-list-endpoints"; import thoughtsData from "./data.json" with { type: "json" }; -const mongoUrl = process.env.MONGODB_URI; +const mongoUrl = process.env.MONGO_URL; +if (!mongoUrl) throw new Error("MONGO_URL is missing"); mongoose.connect(mongoUrl); // Defines the port the app will run on. Defaults to 8080, but can be overridden @@ -20,7 +22,43 @@ app.use(cors()); app.use(express.json()); // Mongoose schema +const thoughtsSchema = new mongoose.Schema({ + message: { + type: String, + required: [true, "Message is required"], + trim: true, + minLength: [5, "Message must be at least 5 characters"], + maxLength: [140, "Message cannot be longer than 140 characters"], + }, + hearts: { + type: Number, + default: 0, + min: [0, "Hearts cannot be negative"], + }, + createdAt: { + type: Date, + default: Date.now, + }, +}); + +const Thought = mongoose.model("Thought", thoughtsSchema); + +if (process.env.RESET_DB === "true") { + const seedDatabase = async () => { + await Thought.deleteMany(); + + thoughtsData.forEach((thought) => { + new Thought({ + message: thought.message, + hearts: thought.hearts, + createdAt: thought.createdAt, + }).save(); + }); + }; + console.log("seeding database"); + seedDatabase(); +} // === ROUTES === // Root endpoint: returns API documentation From 84f994d4033cce11f350e291780211e699c8c33c Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Fri, 30 Jan 2026 14:24:20 +0100 Subject: [PATCH 06/14] Add model and GET/POST endpoints --- server.js | 126 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 89 insertions(+), 37 deletions(-) diff --git a/server.js b/server.js index b77f000f..84df6f77 100644 --- a/server.js +++ b/server.js @@ -2,7 +2,6 @@ import cors from "cors"; import express from "express"; import mongoose from "mongoose"; import "dotenv/config"; -console.log("RESET_DB:", process.env.RESET_DB); import listEndpoints from "express-list-endpoints"; import thoughtsData from "./data.json" with { type: "json" }; @@ -27,8 +26,8 @@ const thoughtsSchema = new mongoose.Schema({ type: String, required: [true, "Message is required"], trim: true, - minLength: [5, "Message must be at least 5 characters"], - maxLength: [140, "Message cannot be longer than 140 characters"], + minlength: [5, "Message must be at least 5 characters"], + maxlength: [140, "Message cannot be longer than 140 characters"], }, hearts: { type: Number, @@ -80,57 +79,110 @@ app.get("/", (req, res) => { // - sort: if set to "createdAt", sorts by newest first // - limit: limits the number of results returned -app.get("/thoughts", (req, res) => { +app.get("/thoughts", async (req, res) => { const { minHearts, search, limit, sort } = req.query; - // start with all thoughts then apply filters - let filteredThoughts = thoughtsData; + const query = {}; - // Filter by minimum hearts if (minHearts) { - filteredThoughts = filteredThoughts.filter( - (thought) => thought.hearts >= Number(minHearts), - ); + query.hearts = { $gte: Number(minHearts) }; //$gte-greater than or equal to } - - // Filter by search text ( case-insensitive) if (search) { - filteredThoughts = filteredThoughts.filter((thought) => - thought.message.toLowerCase().includes(search.toLowerCase()), - ); + query.message = { $regex: search, $options: "i" }; //i for case-insensitive, regex=regular expression) for pattern matching, options for additional settings } - // Sort by createdAt (ex. newest first) - if (sort === "createdAt") { - filteredThoughts.sort( - (a, b) => new Date(b.createdAt) - new Date(a.createdAt), - ); - } + const sortOptions = sort === "createdAt" ? { createdAt: -1 } : {}; - // Limit the number of results - if (limit) { - filteredThoughts = filteredThoughts.slice(0, Number(limit)); - } + try { + let thoughtsQuery = Thought.find(query).sort(sortOptions); + + if (limit) { + thoughtsQuery = thoughtsQuery.limit(Number(limit)); + } + + const thoughts = await thoughtsQuery; - res.json(filteredThoughts); + if (thoughts.length === 0) { + return res.status(404).json({ + success: false, + response: [], + message: "No thoughts found matching the criteria", + }); + } + return res.status(200).json({ + success: true, + response: thoughts, + message: "Thoughts retrieved successfully", + }); + } catch (error) { + return res.status(500).json({ + success: false, + response: [], + message: "An error occurred while retrieving thoughts", + }); + } }); // Get a single thought by id -app.get("/thoughts/:id", (req, res) => { +app.get("/thoughts/:id", async (req, res) => { const { id } = req.params; - // Find thought with matching id - const thought = thoughtsData.find((thought) => thought._id === id); - - // If not found, return 404 error - if (!thought) { - return res - .status(404) - .json({ error: `thought with id ${id} does not exist` }); + try { + if (!mongoose.Types.ObjectId.isValid(id)) { + return res.status(400).json({ + success: false, + response: null, + message: "Invalid ID format", + }); + } + + const thought = await Thought.findById(id); + + if (!thought) { + return res.status(404).json({ + success: false, + response: null, + message: "Thought not found", + }); + } + + return res.status(200).json({ + success: true, + response: thought, + message: "Thought retrieved successfully", + }); + } catch (error) { + return res.status(500).json({ + success: false, + response: null, + message: "An error occurred while retrieving the thought", + }); } +}); + +app.post("/thoughts", async (req, res) => { + const body = req.body; + + try { + const newThought = new Thought({ + message: body.message, + hearts: body.hearts, + }); - // Return the found thought - res.json(thought); + const createdThought = await newThought.save(); + + return res.status(201).json({ + success: true, + response: createdThought, + message: "Thought created successfully", + }); + } catch (error) { + return res.status(400).json({ + success: false, + response: null, + message: error.message, + }); + } }); // Start the server and listen on the specified port From 45bbad68b03525b4c6ed778343f03e0e028ddadd Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Sat, 31 Jan 2026 12:13:21 +0100 Subject: [PATCH 07/14] added Thought model with GET, POST, DELETE and like endpoints --- server.js | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/server.js b/server.js index 84df6f77..36f40b27 100644 --- a/server.js +++ b/server.js @@ -185,6 +185,83 @@ app.post("/thoughts", async (req, res) => { } }); +app.delete("/thoughts/:id", async (req, res) => { + const { id } = req.params; + + try { + if (!mongoose.Types.ObjectId.isValid(id)) { + return res.status(400).json({ + success: false, + response: null, + message: "Invalid ID format", + }); + } + + const deletedThought = await Thought.findByIdAndDelete(id); + + if (!deletedThought) { + return res.status(404).json({ + success: false, + response: null, + message: "Thought not found", + }); + } + + return res.status(200).json({ + success: true, + response: deletedThought, + message: "Thought deleted successfully", + }); + } catch (error) { + console.log("Delete error:", error); + return res.status(500).json({ + success: false, + response: null, + message: "Failed to delete thought", + }); + } +}); + +app.patch("/thoughts/:id/like", async (req, res) => { + const { id } = req.params; + + try { + if (!mongoose.Types.ObjectId.isValid(id)) { + return res.status(400).json({ + success: false, + response: null, + message: "Invalid ID format", + }); + } + + const updatedThought = await Thought.findByIdAndUpdate( + id, + { $inc: { hearts: 1 } }, // $inc is a MongoDB operator, increments the hearts field by 1 and saves the updated value in the database + { new: true, runValidators: true }, + ); + + if (!updatedThought) { + return res.status(404).json({ + success: false, + response: null, + message: "Thought not found", + }); + } + return res.status(200).json({ + success: true, + response: updatedThought, + message: "Thought liked successfully", + }); + } catch (error) { + console.log("LIKE error:", error); + return res.status(500).json({ + success: false, + response: null, + message: "An error occurred while liking the thought", + }); + } +}); + // Start the server and listen on the specified port app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); From 1ee4e05935c63f47ce8e1b10075f4466d2c329bb Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Sun, 1 Feb 2026 14:40:11 +0100 Subject: [PATCH 08/14] add PATCH endpoint to update thoughts with validation --- server.js | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/server.js b/server.js index 36f40b27..b6c164a0 100644 --- a/server.js +++ b/server.js @@ -222,6 +222,55 @@ app.delete("/thoughts/:id", async (req, res) => { } }); +app.patch("/thoughts/:id", async (req, res) => { + const { id } = req.params; + const { message } = req.body; + + if (!message) { + return res.status(400).json({ + success: false, + response: null, + message: "Message is required for update", + }); + } + + if (!mongoose.Types.ObjectId.isValid(id)) { + return res.status(400).json({ + success: false, + response: null, + message: "Invalid ID format", + }); + } + + try { + const updatedThought = await Thought.findByIdAndUpdate( + id, + { message }, + { new: true, runValidators: true }, + ); + + if (!updatedThought) { + return res.status(404).json({ + success: false, + response: null, + message: "Thought not found", + }); + } + + return res.status(200).json({ + success: true, + response: updatedThought, + message: "Thought updated successfully", + }); + } catch (error) { + return res.status(400).json({ + success: false, + response: null, + message: error.message, + }); + } +}); + app.patch("/thoughts/:id/like", async (req, res) => { const { id } = req.params; From 8ceaff046777974220b560eb5a0b6516cdef3d92 Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Mon, 2 Feb 2026 15:14:55 +0100 Subject: [PATCH 09/14] added login logic and user authentication flow --- package.json | 1 + server.js | 148 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 142 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index ad1c94c3..fe038cc2 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@babel/core": "^7.28.6", "@babel/node": "^7.28.6", "@babel/preset-env": "^7.28.6", + "bcrypt": "^6.0.0", "cors": "^2.8.5", "dotenv": "^17.2.3", "express": "^4.22.1", diff --git a/server.js b/server.js index b6c164a0..3d9b40a7 100644 --- a/server.js +++ b/server.js @@ -1,13 +1,21 @@ -import cors from "cors"; +import "dotenv/config"; import express from "express"; +import cors from "cors"; import mongoose from "mongoose"; -import "dotenv/config"; +import bcrypt from "bcrypt"; +import crypto from "crypto"; import listEndpoints from "express-list-endpoints"; import thoughtsData from "./data.json" with { type: "json" }; -const mongoUrl = process.env.MONGO_URL; -if (!mongoUrl) throw new Error("MONGO_URL is missing"); -mongoose.connect(mongoUrl); +const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/auth"; + +try { + await mongoose.connect(mongoUrl); + console.log("Connected to MongoDB"); +} catch (error) { + console.error("MongoDB connection error:", error); + process.exit(1); +} // 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 +27,61 @@ const app = express(); //middlewares to enable cors and json body parsing app.use(cors()); app.use(express.json()); +app.use(cors()); +app.use(express.json()); + +// Mongoose schema and model for User +const userSchema = new mongoose.Schema( + { + username: { + type: String, + unique: true, + required: [true, "Username is required"], + trim: true, + }, + email: { + type: String, + unique: true, + trim: true, + lowercase: true, + required: true, + }, + password: { type: String, required: true, minlength: 8 }, + accessToken: { + type: String, + default: () => crypto.randomBytes(128).toString("hex"), + }, + }, + { timestamps: true }, +); + +const User = mongoose.model("User", userSchema); + +const authenticateUser = async (req, res, next) => { + const authHeader = req.header("Authorization") || ""; + const token = authHeader.startsWith("Bearer ") + ? authHeader.slice(7) + : authHeader; + + if (!token) { + return res + .status(401) + .json({ success: false, message: "Authorization token missing" }); + } + + const user = await User.findOne({ accessToken: token }); + + if (!user) { + return res + .status(401) + .json({ success: false, message: "Invalid authorization token" }); + } -// Mongoose schema + req.user = user; + next(); +}; + +// Mongoose schema and model for Thought const thoughtsSchema = new mongoose.Schema({ message: { type: String, @@ -72,6 +133,18 @@ app.get("/", (req, res) => { }); }); +app.get("/secrets", authenticateUser, (req, res) => { + res.json({ + success: true, + secret: "this is a secret message", + user: { + id: req.user._id, + username: req.user.username, + email: req.user.email, + }, + }); +}); + // Get all thoughts (with optional filtering and sorting) // Query parameters: // - minHearts: filter thoughts with at least this many hearts @@ -160,7 +233,68 @@ app.get("/thoughts/:id", async (req, res) => { } }); -app.post("/thoughts", async (req, res) => { +app.post("/users", async (req, res) => { + try { + const { username, email, password } = req.body; + + if (!username || !email || !password) { + return res.status(400).json({ + success: false, + message: "Username, email, and password are required", + }); + } + + const salt = bcrypt.genSaltSync(); + const user = new User({ + username, + email, + password: bcrypt.hashSync(password, salt), + }); + + await user.save(); + + res.status(201).json({ + success: true, + message: "User created successfully", + userId: user._id, + accessToken: user.accessToken, + }); + } catch (error) { + // Duplicate key error from MongoDB (unique constraints) + if (error?.code === 11000) { + return res.status(409).json({ + success: false, + message: "Username or email already exists", + }); + } + return res.status(400).json({ + success: false, + message: "An error occurred while creating the user", + }); + } +}); +// TODO: fixa bugg med login/ secrets accesstoken +app.post("/sessions", async (req, res) => { + const { username, password, email } = req.body; + + const user = await User.findOne({ email: (email || "").toLowerCase() }); + + if (user && bcrypt.compareSync(password, user.password)) { + res.status(200).json({ + success: true, + message: "Login successful", + userId: user._id, + accessToken: user.accessToken, + }); + } else { + res.status(401).json({ + success: false, + message: "Invalid credentials", + }); + } +}); + +app.post("/thoughts", authenticateUser, async (req, res) => { const body = req.body; try { From 85ead2debcfc771791e54e01c8d013daa1bbd668 Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Tue, 3 Feb 2026 15:21:49 +0100 Subject: [PATCH 10/14] Add user and thought models, routes and auth middleware --- middleware/authMiddleware.js | 26 +++ models/Thoughts.js | 23 ++ models/User.js | 31 +++ routes/thoughtRoutes.js | 241 ++++++++++++++++++++ routes/userRoutes.js | 87 ++++++++ server.js | 418 +---------------------------------- 6 files changed, 415 insertions(+), 411 deletions(-) create mode 100644 middleware/authMiddleware.js create mode 100644 models/Thoughts.js create mode 100644 models/User.js create mode 100644 routes/thoughtRoutes.js create mode 100644 routes/userRoutes.js diff --git a/middleware/authMiddleware.js b/middleware/authMiddleware.js new file mode 100644 index 00000000..b1b4f26b --- /dev/null +++ b/middleware/authMiddleware.js @@ -0,0 +1,26 @@ +import { User } from "../models/User.js"; + +export const authenticateUser = async (req, res, next) => { + try { + const user = await User.findOne({ + accessToken: req.header("Authorization").replace("Bearer ", ""), + }); + + if (user) { + req.user = user; + next(); + } else { + res.status(401).json({ + success: false, + message: "Unauthorized: Invalid or missing access token", + loggedOut: true, + }); + } + } catch (error) { + res.status(500).json({ + success: false, + message: "Internal server error", + error: error.message, + }); + } +}; diff --git a/models/Thoughts.js b/models/Thoughts.js new file mode 100644 index 00000000..c7399707 --- /dev/null +++ b/models/Thoughts.js @@ -0,0 +1,23 @@ +import mongoose, { Schema } from "mongoose"; + +// Mongoose schema and model for Thought +const thoughtsSchema = new mongoose.Schema({ + message: { + type: String, + required: [true, "Message is required"], + trim: true, + minlength: [5, "Message must be at least 5 characters"], + maxlength: [140, "Message cannot be longer than 140 characters"], + }, + hearts: { + type: Number, + default: 0, + min: [0, "Hearts cannot be negative"], + }, + createdAt: { + type: Date, + default: Date.now, + }, +}); + +export const Thoughts = mongoose.model("Thoughts", thoughtsSchema); diff --git a/models/User.js b/models/User.js new file mode 100644 index 00000000..2b2fe4e0 --- /dev/null +++ b/models/User.js @@ -0,0 +1,31 @@ +import mongoose, { Schema } from "mongoose"; +import crypto from "crypto"; + +const UserSchema = new Schema( + { + username: { + type: String, + required: true, + unique: true, + trim: true, + }, + email: { + type: String, + required: true, + unique: true, + trim: true, + lowercase: true, + }, + password: { + type: String, + required: true, + }, + accessToken: { + type: String, + default: () => crypto.randomBytes(128).toString("hex"), + }, + }, + { timestamps: true }, +); + +export const User = mongoose.model("User", UserSchema); diff --git a/routes/thoughtRoutes.js b/routes/thoughtRoutes.js new file mode 100644 index 00000000..e734090c --- /dev/null +++ b/routes/thoughtRoutes.js @@ -0,0 +1,241 @@ +import express from "express"; +import mongoose from "mongoose"; +import { Thoughts } from "../models/Thoughts.js"; +import { authenticateUser } from "../middleware/authMiddleware.js"; + +const router = express.Router(); + +// Get all thoughts with optional filters +router.get("/", async (req, res) => { + const { minHearts, search, limit, sort } = req.query; + + const query = {}; + + if (minHearts) { + query.hearts = { $gte: Number(minHearts) }; //$gte-greater than or equal to + } + if (search) { + query.message = { $regex: search, $options: "i" }; //i for case-insensitive, regex=regular expression) for pattern matching, options for additional settings + } + + const sortOptions = sort === "createdAt" ? { createdAt: -1 } : {}; + + try { + let thoughtsQuery = Thoughts.find(query).sort(sortOptions); + + if (limit) { + thoughtsQuery = thoughtsQuery.limit(Number(limit)); + } + + const thoughts = await thoughtsQuery; + + if (thoughts.length === 0) { + return res.status(404).json({ + success: false, + response: [], + message: "No thoughts found matching the criteria", + }); + } + return res.status(200).json({ + success: true, + response: thoughts, + message: "Thoughts retrieved successfully", + }); + } catch (error) { + return res.status(500).json({ + success: false, + response: [], + message: "An error occurred while retrieving thoughts", + }); + } +}); + +// Get a single thought by id +router.get("/:id", async (req, res) => { + const { id } = req.params; + + try { + if (!mongoose.Types.ObjectId.isValid(id)) { + return res.status(400).json({ + success: false, + response: null, + message: "Invalid ID format", + }); + } + + const thought = await Thoughts.findById(id); + + if (!thought) { + return res.status(404).json({ + success: false, + response: null, + message: "Thought not found", + }); + } + + return res.status(200).json({ + success: true, + response: thought, + message: "Thought retrieved successfully", + }); + } catch (error) { + return res.status(500).json({ + success: false, + response: null, + message: "An error occurred while retrieving the thought", + }); + } +}); + +router.post("/", authenticateUser, async (req, res) => { + const body = req.body; + + try { + const newThought = new Thought({ + message: body.message, + hearts: body.hearts, + }); + + const createdThought = await newThought.save(); + + return res.status(201).json({ + success: true, + response: createdThought, + message: "Thought created successfully", + }); + } catch (error) { + return res.status(400).json({ + success: false, + response: null, + message: error.message, + }); + } +}); + +router.delete("/:id", async (req, res) => { + const { id } = req.params; + + try { + if (!mongoose.Types.ObjectId.isValid(id)) { + return res.status(400).json({ + success: false, + response: null, + message: "Invalid ID format", + }); + } + + const deletedThought = await Thoughts.findByIdAndDelete(id); + + if (!deletedThought) { + return res.status(404).json({ + success: false, + response: null, + message: "Thought not found", + }); + } + + return res.status(200).json({ + success: true, + response: deletedThought, + message: "Thought deleted successfully", + }); + } catch (error) { + console.log("Delete error:", error); + return res.status(500).json({ + success: false, + response: null, + message: "Failed to delete thought", + }); + } +}); + +router.patch("/:id", async (req, res) => { + const { id } = req.params; + const { message } = req.body; + + if (!message) { + return res.status(400).json({ + success: false, + response: null, + message: "Message is required for update", + }); + } + + if (!mongoose.Types.ObjectId.isValid(id)) { + return res.status(400).json({ + success: false, + response: null, + message: "Invalid ID format", + }); + } + + try { + const updatedThought = await Thoughts.findByIdAndUpdate( + id, + { message }, + { new: true, runValidators: true }, + ); + + if (!updatedThought) { + return res.status(404).json({ + success: false, + response: null, + message: "Thought not found", + }); + } + + return res.status(200).json({ + success: true, + response: updatedThought, + message: "Thought updated successfully", + }); + } catch (error) { + return res.status(400).json({ + success: false, + response: null, + message: error.message, + }); + } +}); + +router.patch("/:id/like", async (req, res) => { + const { id } = req.params; + + try { + if (!mongoose.Types.ObjectId.isValid(id)) { + return res.status(400).json({ + success: false, + response: null, + message: "Invalid ID format", + }); + } + + const updatedThought = await Thoughts.findByIdAndUpdate( + id, + { $inc: { hearts: 1 } }, // $inc is a MongoDB operator, increments the hearts field by 1 and saves the updated value in the database + { new: true, runValidators: true }, + ); + + if (!updatedThought) { + return res.status(404).json({ + success: false, + response: null, + message: "Thought not found", + }); + } + return res.status(200).json({ + success: true, + response: updatedThought, + message: "Thought liked successfully", + }); + } catch (error) { + console.log("LIKE error:", error); + return res.status(500).json({ + success: false, + response: null, + message: "An error occurred while liking the thought", + }); + } +}); + +export default router; diff --git a/routes/userRoutes.js b/routes/userRoutes.js new file mode 100644 index 00000000..6b62a0ea --- /dev/null +++ b/routes/userRoutes.js @@ -0,0 +1,87 @@ +import express, { response } from "express"; +import bcrypt from "bcrypt"; +import { User } from "../models/User.js"; + +const router = express.Router(); + +router.post("/user-signup", async (req, res) => { + try { + const { username, email, password } = req.body; + + if (!username || !email || !password) { + return res.status(400).json({ + success: false, + message: "Username, email, and password are required", + }); + } + + const existingUser = await User.findOne({ email: email.toLowerCase() }); + + if (existingUser) { + return res.status(409).json({ + success: false, + message: "An error occurred when creating the user", + }); + } + + const salt = bcrypt.genSaltSync(); + const hashedPassword = bcrypt.hashSync(password, salt); + const user = new User({ + username, + email: email.toLowerCase(), + password: hashedPassword, + }); + + await user.save(); + + res.status(201).json({ + success: true, + message: "User created successfully", + response: { + email: user.email, + userId: user._id, + accessToken: user.accessToken, + }, + }); + } catch (error) { + res.status(400).json({ + success: false, + message: "Failed to create user", + response: error, + }); + } +}); + +router.post("/user-login", async (req, res) => { + try { + const { email, password } = req.body; + + const user = await User.findOne({ email: email.toLowerCase() }); + + if (user && bcrypt.compareSync(password, user.password)) { + res.json({ + success: true, + message: "Login successful", + response: { + email: user.email, + userId: user._id, + accessToken: user.accessToken, + }, + }); + } else { + res.status(401).json({ + success: false, + message: "Invalid email or password", + response: null, + }); + } + } catch (error) { + res.status(400).json({ + success: false, + message: "Login failed", + response: error, + }); + } +}); + +export default router; diff --git a/server.js b/server.js index 3d9b40a7..fa0bff0d 100644 --- a/server.js +++ b/server.js @@ -2,12 +2,14 @@ import "dotenv/config"; import express from "express"; import cors from "cors"; import mongoose from "mongoose"; -import bcrypt from "bcrypt"; -import crypto from "crypto"; + +import userRoutes from "./routes/userRoutes.js"; +import thoughtRoutes from "./routes/thoughtRoutes.js"; + import listEndpoints from "express-list-endpoints"; import thoughtsData from "./data.json" with { type: "json" }; -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/auth"; +const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/happy-thoughts"; try { await mongoose.connect(mongoUrl); @@ -27,423 +29,17 @@ const app = express(); //middlewares to enable cors and json body parsing app.use(cors()); app.use(express.json()); -app.use(cors()); -app.use(express.json()); - -// Mongoose schema and model for User -const userSchema = new mongoose.Schema( - { - username: { - type: String, - unique: true, - required: [true, "Username is required"], - trim: true, - }, - email: { - type: String, - unique: true, - trim: true, - lowercase: true, - required: true, - }, - password: { type: String, required: true, minlength: 8 }, - accessToken: { - type: String, - default: () => crypto.randomBytes(128).toString("hex"), - }, - }, - { timestamps: true }, -); - -const User = mongoose.model("User", userSchema); - -const authenticateUser = async (req, res, next) => { - const authHeader = req.header("Authorization") || ""; - const token = authHeader.startsWith("Bearer ") - ? authHeader.slice(7) - : authHeader; - - if (!token) { - return res - .status(401) - .json({ success: false, message: "Authorization token missing" }); - } - - const user = await User.findOne({ accessToken: token }); - - if (!user) { - return res - .status(401) - .json({ success: false, message: "Invalid authorization token" }); - } - - req.user = user; - next(); -}; - -// Mongoose schema and model for Thought -const thoughtsSchema = new mongoose.Schema({ - message: { - type: String, - required: [true, "Message is required"], - trim: true, - minlength: [5, "Message must be at least 5 characters"], - maxlength: [140, "Message cannot be longer than 140 characters"], - }, - hearts: { - type: Number, - default: 0, - min: [0, "Hearts cannot be negative"], - }, - createdAt: { - type: Date, - default: Date.now, - }, -}); - -const Thought = mongoose.model("Thought", thoughtsSchema); - -if (process.env.RESET_DB === "true") { - const seedDatabase = async () => { - await Thought.deleteMany(); - - thoughtsData.forEach((thought) => { - new Thought({ - message: thought.message, - hearts: thought.hearts, - createdAt: thought.createdAt, - }).save(); - }); - }; - - console.log("seeding database"); - seedDatabase(); -} -// === ROUTES === - -// Root endpoint: returns API documentation -// Lists all available endpoints using express-list-endpoints app.get("/", (req, res) => { const endpoints = listEndpoints(app); - res.json({ message: "Welcome to my Happy Thoughts API.", endpoints: endpoints, }); }); -app.get("/secrets", authenticateUser, (req, res) => { - res.json({ - success: true, - secret: "this is a secret message", - user: { - id: req.user._id, - username: req.user.username, - email: req.user.email, - }, - }); -}); - -// Get all thoughts (with optional filtering and sorting) -// Query parameters: -// - minHearts: filter thoughts with at least this many hearts -// - search: filter thoughts containing this text (case-insensitive) -// - sort: if set to "createdAt", sorts by newest first -// - limit: limits the number of results returned - -app.get("/thoughts", async (req, res) => { - const { minHearts, search, limit, sort } = req.query; - - const query = {}; - - if (minHearts) { - query.hearts = { $gte: Number(minHearts) }; //$gte-greater than or equal to - } - if (search) { - query.message = { $regex: search, $options: "i" }; //i for case-insensitive, regex=regular expression) for pattern matching, options for additional settings - } - - const sortOptions = sort === "createdAt" ? { createdAt: -1 } : {}; - - try { - let thoughtsQuery = Thought.find(query).sort(sortOptions); - - if (limit) { - thoughtsQuery = thoughtsQuery.limit(Number(limit)); - } - - const thoughts = await thoughtsQuery; - - if (thoughts.length === 0) { - return res.status(404).json({ - success: false, - response: [], - message: "No thoughts found matching the criteria", - }); - } - return res.status(200).json({ - success: true, - response: thoughts, - message: "Thoughts retrieved successfully", - }); - } catch (error) { - return res.status(500).json({ - success: false, - response: [], - message: "An error occurred while retrieving thoughts", - }); - } -}); - -// Get a single thought by id -app.get("/thoughts/:id", async (req, res) => { - const { id } = req.params; - - try { - if (!mongoose.Types.ObjectId.isValid(id)) { - return res.status(400).json({ - success: false, - response: null, - message: "Invalid ID format", - }); - } - - const thought = await Thought.findById(id); - - if (!thought) { - return res.status(404).json({ - success: false, - response: null, - message: "Thought not found", - }); - } - - return res.status(200).json({ - success: true, - response: thought, - message: "Thought retrieved successfully", - }); - } catch (error) { - return res.status(500).json({ - success: false, - response: null, - message: "An error occurred while retrieving the thought", - }); - } -}); - -app.post("/users", async (req, res) => { - try { - const { username, email, password } = req.body; - - if (!username || !email || !password) { - return res.status(400).json({ - success: false, - message: "Username, email, and password are required", - }); - } - - const salt = bcrypt.genSaltSync(); - const user = new User({ - username, - email, - password: bcrypt.hashSync(password, salt), - }); - - await user.save(); - - res.status(201).json({ - success: true, - message: "User created successfully", - userId: user._id, - accessToken: user.accessToken, - }); - } catch (error) { - // Duplicate key error from MongoDB (unique constraints) - if (error?.code === 11000) { - return res.status(409).json({ - success: false, - message: "Username or email already exists", - }); - } - return res.status(400).json({ - success: false, - message: "An error occurred while creating the user", - }); - } -}); -// TODO: fixa bugg med login/ secrets accesstoken -app.post("/sessions", async (req, res) => { - const { username, password, email } = req.body; - - const user = await User.findOne({ email: (email || "").toLowerCase() }); - - if (user && bcrypt.compareSync(password, user.password)) { - res.status(200).json({ - success: true, - message: "Login successful", - userId: user._id, - accessToken: user.accessToken, - }); - } else { - res.status(401).json({ - success: false, - message: "Invalid credentials", - }); - } -}); - -app.post("/thoughts", authenticateUser, async (req, res) => { - const body = req.body; - - try { - const newThought = new Thought({ - message: body.message, - hearts: body.hearts, - }); - - const createdThought = await newThought.save(); - - return res.status(201).json({ - success: true, - response: createdThought, - message: "Thought created successfully", - }); - } catch (error) { - return res.status(400).json({ - success: false, - response: null, - message: error.message, - }); - } -}); - -app.delete("/thoughts/:id", async (req, res) => { - const { id } = req.params; - - try { - if (!mongoose.Types.ObjectId.isValid(id)) { - return res.status(400).json({ - success: false, - response: null, - message: "Invalid ID format", - }); - } - - const deletedThought = await Thought.findByIdAndDelete(id); - - if (!deletedThought) { - return res.status(404).json({ - success: false, - response: null, - message: "Thought not found", - }); - } - - return res.status(200).json({ - success: true, - response: deletedThought, - message: "Thought deleted successfully", - }); - } catch (error) { - console.log("Delete error:", error); - return res.status(500).json({ - success: false, - response: null, - message: "Failed to delete thought", - }); - } -}); - -app.patch("/thoughts/:id", async (req, res) => { - const { id } = req.params; - const { message } = req.body; - - if (!message) { - return res.status(400).json({ - success: false, - response: null, - message: "Message is required for update", - }); - } - - if (!mongoose.Types.ObjectId.isValid(id)) { - return res.status(400).json({ - success: false, - response: null, - message: "Invalid ID format", - }); - } - - try { - const updatedThought = await Thought.findByIdAndUpdate( - id, - { message }, - { new: true, runValidators: true }, - ); - - if (!updatedThought) { - return res.status(404).json({ - success: false, - response: null, - message: "Thought not found", - }); - } - - return res.status(200).json({ - success: true, - response: updatedThought, - message: "Thought updated successfully", - }); - } catch (error) { - return res.status(400).json({ - success: false, - response: null, - message: error.message, - }); - } -}); - -app.patch("/thoughts/:id/like", async (req, res) => { - const { id } = req.params; - - try { - if (!mongoose.Types.ObjectId.isValid(id)) { - return res.status(400).json({ - success: false, - response: null, - message: "Invalid ID format", - }); - } - - const updatedThought = await Thought.findByIdAndUpdate( - id, - { $inc: { hearts: 1 } }, // $inc is a MongoDB operator, increments the hearts field by 1 and saves the updated value in the database - { new: true, runValidators: true }, - ); - - if (!updatedThought) { - return res.status(404).json({ - success: false, - response: null, - message: "Thought not found", - }); - } - return res.status(200).json({ - success: true, - response: updatedThought, - message: "Thought liked successfully", - }); - } catch (error) { - console.log("LIKE error:", error); - return res.status(500).json({ - success: false, - response: null, - message: "An error occurred while liking the thought", - }); - } -}); +app.use("/users", userRoutes); +app.use("/thoughts", thoughtRoutes); // Start the server and listen on the specified port app.listen(port, () => { From baf4e88db72c18190f5fd3cdfeb53e8c3e79688a Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Tue, 3 Feb 2026 21:06:27 +0100 Subject: [PATCH 11/14] Fix Thought model import and enable thoughts routes --- routes/thoughtRoutes.js | 2 +- server.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/routes/thoughtRoutes.js b/routes/thoughtRoutes.js index e734090c..fa1a2c22 100644 --- a/routes/thoughtRoutes.js +++ b/routes/thoughtRoutes.js @@ -91,7 +91,7 @@ router.post("/", authenticateUser, async (req, res) => { const body = req.body; try { - const newThought = new Thought({ + const newThought = new Thoughts({ message: body.message, hearts: body.hearts, }); diff --git a/server.js b/server.js index fa0bff0d..236fd16b 100644 --- a/server.js +++ b/server.js @@ -9,7 +9,7 @@ import thoughtRoutes from "./routes/thoughtRoutes.js"; import listEndpoints from "express-list-endpoints"; import thoughtsData from "./data.json" with { type: "json" }; -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/happy-thoughts"; +const mongoUrl = process.env.MONGO_URL; try { await mongoose.connect(mongoUrl); From c2d45eb0718c8a950929fad74d60198cc64dcfea Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Wed, 4 Feb 2026 13:54:10 +0100 Subject: [PATCH 12/14] Included username in auth responses and clean up backend --- routes/userRoutes.js | 4 +++- server.js | 8 ++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/routes/userRoutes.js b/routes/userRoutes.js index 6b62a0ea..e4b3c203 100644 --- a/routes/userRoutes.js +++ b/routes/userRoutes.js @@ -1,4 +1,4 @@ -import express, { response } from "express"; +import express from "express"; import bcrypt from "bcrypt"; import { User } from "../models/User.js"; @@ -38,6 +38,7 @@ router.post("/user-signup", async (req, res) => { success: true, message: "User created successfully", response: { + username: user.username, email: user.email, userId: user._id, accessToken: user.accessToken, @@ -63,6 +64,7 @@ router.post("/user-login", async (req, res) => { success: true, message: "Login successful", response: { + username: user.username, email: user.email, userId: user._id, accessToken: user.accessToken, diff --git a/server.js b/server.js index 236fd16b..a6ff64dc 100644 --- a/server.js +++ b/server.js @@ -2,12 +2,9 @@ import "dotenv/config"; import express from "express"; import cors from "cors"; import mongoose from "mongoose"; - import userRoutes from "./routes/userRoutes.js"; import thoughtRoutes from "./routes/thoughtRoutes.js"; - import listEndpoints from "express-list-endpoints"; -import thoughtsData from "./data.json" with { type: "json" }; const mongoUrl = process.env.MONGO_URL; @@ -30,11 +27,10 @@ const app = express(); app.use(cors()); app.use(express.json()); -app.get("/", (req, res) => { - const endpoints = listEndpoints(app); +app.get("/", (_req, res) => { res.json({ message: "Welcome to my Happy Thoughts API.", - endpoints: endpoints, + endpoints, }); }); From 84f1c2969177c5c27af437b387fb9ad2e0a27b40 Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Wed, 4 Feb 2026 16:15:24 +0100 Subject: [PATCH 13/14] minor adjustments to user and thought routes --- routes/thoughtRoutes.js | 7 +++---- routes/userRoutes.js | 9 +++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/routes/thoughtRoutes.js b/routes/thoughtRoutes.js index fa1a2c22..7788612f 100644 --- a/routes/thoughtRoutes.js +++ b/routes/thoughtRoutes.js @@ -112,7 +112,7 @@ router.post("/", authenticateUser, async (req, res) => { } }); -router.delete("/:id", async (req, res) => { +router.delete("/:id", authenticateUser, async (req, res) => { const { id } = req.params; try { @@ -140,8 +140,7 @@ router.delete("/:id", async (req, res) => { message: "Thought deleted successfully", }); } catch (error) { - console.log("Delete error:", error); - return res.status(500).json({ + res.status(500).json({ success: false, response: null, message: "Failed to delete thought", @@ -149,7 +148,7 @@ router.delete("/:id", async (req, res) => { } }); -router.patch("/:id", async (req, res) => { +router.patch("/:id", authenticateUser, async (req, res) => { const { id } = req.params; const { message } = req.body; diff --git a/routes/userRoutes.js b/routes/userRoutes.js index e4b3c203..96ee9e28 100644 --- a/routes/userRoutes.js +++ b/routes/userRoutes.js @@ -15,6 +15,15 @@ router.post("/user-signup", async (req, res) => { }); } + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + + if (!emailRegex.test(email)) { + return res.status(400).json({ + success: false, + message: "invalid email format", + }); + } + const existingUser = await User.findOne({ email: email.toLowerCase() }); if (existingUser) { From 1f8cf96436a741944e5afea7cc4c12f14321324c Mon Sep 17 00:00:00 2001 From: Sara Enderborg Date: Wed, 4 Feb 2026 21:57:59 +0100 Subject: [PATCH 14/14] fixed endpoints bugg --- server.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/server.js b/server.js index a6ff64dc..d8b89ca2 100644 --- a/server.js +++ b/server.js @@ -16,17 +16,14 @@ try { process.exit(1); } -// 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 = 8080; // This would break on most hosting platforms! const port = process.env.PORT || 8080; const app = express(); -//middlewares to enable cors and json body parsing app.use(cors()); app.use(express.json()); +const endpoints = listEndpoints(app); + app.get("/", (_req, res) => { res.json({ message: "Welcome to my Happy Thoughts API.", @@ -37,7 +34,6 @@ app.get("/", (_req, res) => { app.use("/users", userRoutes); app.use("/thoughts", thoughtRoutes); -// Start the server and listen on the specified port app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); });