From be10ac29961bf238e61eda6025c00b308bce255a Mon Sep 17 00:00:00 2001 From: tildetilde Date: Tue, 27 May 2025 18:59:03 +0200 Subject: [PATCH 01/22] Loading thoughts from data.json --- data.json | 12 ++++++------ server.js | 33 +++++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/data.json b/data.json index a2c844f..11ff432 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/server.js b/server.js index f47771b..a0a7b2e 100644 --- a/server.js +++ b/server.js @@ -1,22 +1,35 @@ -import cors from "cors" -import express from "express" +import cors from "cors"; +import express from "express"; +import fs from "fs"; // 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()); + +// Function to load thoughts from a JSON file +const loadThoughts = () => { + const data = fs.readFileSync("data.json"); + return JSON.parse(data); +}; // Start defining your routes here app.get("/", (req, res) => { - res.send("Hello Technigo!") -}) + res.send("Hello Technigo!"); +}); + +app.get("/thoughts", (req, res) => { + const thoughts = loadThoughts(); + + res.json(thoughts); +}); // Start the server app.listen(port, () => { - console.log(`Server running on http://localhost:${port}`) -}) + console.log(`Server running on http://localhost:${port}`); +}); From d20f84a6e71a9df77a95f512bcb588fdcc603d96 Mon Sep 17 00:00:00 2001 From: tildetilde Date: Wed, 28 May 2025 15:13:17 +0200 Subject: [PATCH 02/22] Created find thought by id --- controllers/thoughtsController.js | 0 package.json | 1 + routes/thoughts.js | 0 server.js | 20 +++++++++++++++++++- utils/loadThoughts.js | 0 5 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 controllers/thoughtsController.js create mode 100644 routes/thoughts.js create mode 100644 utils/loadThoughts.js diff --git a/controllers/thoughtsController.js b/controllers/thoughtsController.js new file mode 100644 index 0000000..e69de29 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/routes/thoughts.js b/routes/thoughts.js new file mode 100644 index 0000000..e69de29 diff --git a/server.js b/server.js index a0a7b2e..0241ddc 100644 --- a/server.js +++ b/server.js @@ -1,6 +1,7 @@ import cors from "cors"; import express from "express"; import fs from "fs"; +import expressListEndpoints from "express-list-endpoints"; // 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: @@ -20,7 +21,11 @@ const loadThoughts = () => { // Start defining your routes here app.get("/", (req, res) => { - res.send("Hello Technigo!"); + const endpoints = expressListEndpoints(app); + res.json({ + message: "This is my API", + endpoints: endpoints, + }); }); app.get("/thoughts", (req, res) => { @@ -29,6 +34,19 @@ app.get("/thoughts", (req, res) => { res.json(thoughts); }); +app.get("/thoughts/:id", (req, res) => { + const thoughts = loadThoughts(); + const id = req.params.id; + + const thought = thoughts.find((t) => t._id === id); + + if (!thought) { + return res.status(404).json({ message: "Thought not found" }); + } + + res.json(thought); +}); + // Start the server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); diff --git a/utils/loadThoughts.js b/utils/loadThoughts.js new file mode 100644 index 0000000..e69de29 From e6c632fbf7744c13ad04957f7c07040f612f4389 Mon Sep 17 00:00:00 2001 From: tildetilde Date: Wed, 28 May 2025 15:19:50 +0200 Subject: [PATCH 03/22] Structuring the project using clean code principles --- controllers/thoughtsController.js | 21 ++++++++++++++++++ data.json => data/data.json | 0 routes/thoughts.js | 16 ++++++++++++++ server.js | 36 ++++--------------------------- utils/loadThoughts.js | 8 +++++++ 5 files changed, 49 insertions(+), 32 deletions(-) rename data.json => data/data.json (100%) diff --git a/controllers/thoughtsController.js b/controllers/thoughtsController.js index e69de29..d9a54dd 100644 --- a/controllers/thoughtsController.js +++ b/controllers/thoughtsController.js @@ -0,0 +1,21 @@ +// controllers/thoughtsController.js +import loadThoughts from "../utils/loadThoughts.js"; + +// @desc Get all thoughts +export const getAllThoughts = (req, res) => { + const thoughts = loadThoughts(); + res.json(thoughts); +}; + +// @desc Get a single thought by ID +export const getThoughtById = (req, res) => { + const { id } = req.params; + const thoughts = loadThoughts(); + const thought = thoughts.find((t) => t._id === id); + + if (!thought) { + return res.status(404).json({ message: "Thought not found" }); + } + + res.json(thought); +}; diff --git a/data.json b/data/data.json similarity index 100% rename from data.json rename to data/data.json diff --git a/routes/thoughts.js b/routes/thoughts.js index e69de29..331bd84 100644 --- a/routes/thoughts.js +++ b/routes/thoughts.js @@ -0,0 +1,16 @@ +// routes/thoughts.js +import express from "express"; +import { + getAllThoughts, + getThoughtById, +} from "../controllers/thoughtsController.js"; + +const router = express.Router(); + +// GET /thoughts – List all thoughts +router.get("/", getAllThoughts); + +// GET /thoughts/:id – Get one thought +router.get("/:id", getThoughtById); + +export default router; diff --git a/server.js b/server.js index 0241ddc..96e59d4 100644 --- a/server.js +++ b/server.js @@ -2,52 +2,24 @@ import cors from "cors"; import express from "express"; import fs from "fs"; import expressListEndpoints from "express-list-endpoints"; +import thoughtsRoutes from "./routes/thoughts.js"; -// 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(); -// Add middlewares to enable cors and json body parsing app.use(cors()); app.use(express.json()); -// Function to load thoughts from a JSON file -const loadThoughts = () => { - const data = fs.readFileSync("data.json"); - return JSON.parse(data); -}; - -// Start defining your routes here app.get("/", (req, res) => { const endpoints = expressListEndpoints(app); res.json({ - message: "This is my API", - endpoints: endpoints, + message: "This is an API", + endpoints, }); }); -app.get("/thoughts", (req, res) => { - const thoughts = loadThoughts(); - - res.json(thoughts); -}); - -app.get("/thoughts/:id", (req, res) => { - const thoughts = loadThoughts(); - const id = req.params.id; - - const thought = thoughts.find((t) => t._id === id); - - if (!thought) { - return res.status(404).json({ message: "Thought not found" }); - } - - res.json(thought); -}); +app.use("/thoughts", thoughtsRoutes); -// Start the server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); diff --git a/utils/loadThoughts.js b/utils/loadThoughts.js index e69de29..c9b418d 100644 --- a/utils/loadThoughts.js +++ b/utils/loadThoughts.js @@ -0,0 +1,8 @@ +import fs from "fs"; + +const loadThoughts = () => { + const data = fs.readFileSync("data/data.json"); + return JSON.parse(data); +}; + +export default loadThoughts; From 62b69523c184d659f792fc4417647a813b001284 Mon Sep 17 00:00:00 2001 From: tildetilde Date: Wed, 28 May 2025 15:27:05 +0200 Subject: [PATCH 04/22] Built pagination based on pages and limit --- controllers/thoughtsController.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/controllers/thoughtsController.js b/controllers/thoughtsController.js index d9a54dd..a01d521 100644 --- a/controllers/thoughtsController.js +++ b/controllers/thoughtsController.js @@ -1,13 +1,27 @@ // controllers/thoughtsController.js import loadThoughts from "../utils/loadThoughts.js"; -// @desc Get all thoughts +// Get all thoughts export const getAllThoughts = (req, res) => { const thoughts = loadThoughts(); - res.json(thoughts); + + const page = parseInt(req.query.page) || 1; + const limit = parseInt(req.query.limit) || 10; + + const startIndex = (page - 1) * limit; + const endIndex = startIndex + limit; + + const paginatedThoughts = thoughts.slice(startIndex, endIndex); + + res.json({ + totalThoughts: thoughts.length, + totalPages: Math.ceil(thoughts.length / limit), + currentPage: page, + thoughts: paginatedThoughts, + }); }; -// @desc Get a single thought by ID +// Get a single thought by ID export const getThoughtById = (req, res) => { const { id } = req.params; const thoughts = loadThoughts(); From 1ef5edd5952339d9f0ba21a0a83e9878c57ab471 Mon Sep 17 00:00:00 2001 From: tildetilde Date: Wed, 28 May 2025 15:46:45 +0200 Subject: [PATCH 05/22] Adding sorting and filtering --- controllers/thoughtsController.js | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/controllers/thoughtsController.js b/controllers/thoughtsController.js index a01d521..0e3ab3c 100644 --- a/controllers/thoughtsController.js +++ b/controllers/thoughtsController.js @@ -4,6 +4,30 @@ import loadThoughts from "../utils/loadThoughts.js"; // Get all thoughts export const getAllThoughts = (req, res) => { const thoughts = loadThoughts(); + let result = [...thoughts]; + + if (req.query.minHearts) { + const minHearts = parseInt(req.query.minHearts); + result = result.filter((t) => t.hearts >= minHearts); + } + + if (req.query.after) { + const afterDate = new Date(req.query.after); + result = result.filter((t) => new Date(t.createdAt) > afterDate); + } + + if (req.query.sortBy) { + const sortBy = req.query.sortBy; + const order = req.query.order === "desc" ? -1 : 1; + result.sort((a, b) => { + if (sortBy === "hearts") { + return (a.hearts - b.hearts) * order; + } else if (sortBy === "createdAt") { + return (new Date(a.createdAt) - new Date(b.createdAt)) * order; + } + return 0; + }); + } const page = parseInt(req.query.page) || 1; const limit = parseInt(req.query.limit) || 10; @@ -11,11 +35,11 @@ export const getAllThoughts = (req, res) => { const startIndex = (page - 1) * limit; const endIndex = startIndex + limit; - const paginatedThoughts = thoughts.slice(startIndex, endIndex); + const paginatedThoughts = result.slice(startIndex, endIndex); res.json({ - totalThoughts: thoughts.length, - totalPages: Math.ceil(thoughts.length / limit), + totalThoughts: result.length, + totalPages: Math.ceil(result.length / limit), currentPage: page, thoughts: paginatedThoughts, }); From f25f5da96988d440c562aee5825ad669ea0677ad Mon Sep 17 00:00:00 2001 From: tildetilde Date: Thu, 29 May 2025 13:52:46 +0200 Subject: [PATCH 06/22] Adding sorting error message --- controllers/thoughtsController.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/controllers/thoughtsController.js b/controllers/thoughtsController.js index 0e3ab3c..556ce0d 100644 --- a/controllers/thoughtsController.js +++ b/controllers/thoughtsController.js @@ -19,6 +19,15 @@ export const getAllThoughts = (req, res) => { if (req.query.sortBy) { const sortBy = req.query.sortBy; const order = req.query.order === "desc" ? -1 : 1; + + const validSortFields = ["hearts", "createdAt"]; + if (!validSortFields.includes(sortBy)) { + return res.status(400).json({ + success: false, + message: "Invalid sort field", + }); + } + result.sort((a, b) => { if (sortBy === "hearts") { return (a.hearts - b.hearts) * order; From 7da754b5c209e8c6fda54cda8bf86d123f4756db Mon Sep 17 00:00:00 2001 From: tildetilde Date: Tue, 3 Jun 2025 15:30:47 +0200 Subject: [PATCH 07/22] Added todo and clearer structure --- TODO.md | 52 +++++++++++++++++++++++++++++++ controllers/thoughtsController.js | 3 ++ middleware/auth.js | 0 utils/saveThoughts.js | 7 +++++ 4 files changed, 62 insertions(+) create mode 100644 TODO.md create mode 100644 middleware/auth.js create mode 100644 utils/saveThoughts.js diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..5b63d5c --- /dev/null +++ b/TODO.md @@ -0,0 +1,52 @@ +# βœ… TODO: Connect Frontend & Backend for Happy Thoughts + +## πŸ₯‡ Step 1: Finalize backend using MongoDB & Mongoose + +- [ ] Create a `Thought` model in `models/Thought.js` + - Fields: `message` (string, required, min/max length), `hearts`, `createdAt` +- [ ] Seed the database with sample thoughts +- [ ] Create route: `GET /thoughts` β†’ return latest (e.g. 20) +- [ ] Create route: `POST /thoughts` β†’ save new thought +- [ ] Create route: `PATCH /thoughts/:id/like` β†’ increment hearts +- [ ] Create route: `DELETE /thoughts/:id` β†’ delete a thought +- [ ] BONUS: `GET /thoughts?page=2` β†’ use `.skip().limit()` for pagination +- [ ] BONUS: `GET /thoughts?category=joy` β†’ filter with query params + +--- + +## πŸ₯ˆ Step 2: Add validation & error handling + +- [ ] Add Mongoose validation in the model (e.g. min/max message length) +- [ ] Use `try/catch` in all routes +- [ ] Return proper status codes (400, 404, 500) with `.status().json() +- [ ] Return useful error messages for the frontend + +--- + +## πŸ₯‰ Step 3: Connect frontend to the API + +- [ ] Change API URL in `happy-thoughts` frontend + ```js + const API_URL = "http://localhost:8080/thoughts"; // or deployed URL + ``` +- Update fetch requests: +- [ ] GET /thoughts β†’ display the thought list +- [ ] POST /thoughts β†’ send a new thought +- [ ] PATCH /thoughts/:id/like β†’ like a thought +- [ ] DELETE /thoughts/:id β†’ delete a thought +- [ ] Show errors and loading states in the UI + +## 🏁 Step 4: Deploy & manage environments + +- [ ] Deploy backend to Render +- [ ] Deploy database to MongoDB Atlas +- [ ] Add .env on Render with MONGO_URL +- [ ] Update the frontend to point to deployed backend + +## 🌈 Step 5: Stretch goals – once core functionality is working + +- [ ] Add filtering using query parameters (/thoughts?tag=joy) +- [ ] Implement pagination using .skip() and .limit() +- [ ] Add infinite scroll to the frontend +- [ ] Sort thoughts with .sort() – by newest or most liked +- [ ] Group thoughts by category (if using categories) diff --git a/controllers/thoughtsController.js b/controllers/thoughtsController.js index 556ce0d..fcb76ce 100644 --- a/controllers/thoughtsController.js +++ b/controllers/thoughtsController.js @@ -1,5 +1,8 @@ +import { v4 as uuidv4 } from "uuid"; + // controllers/thoughtsController.js import loadThoughts from "../utils/loadThoughts.js"; +import saveThoughts from "../utils/saveThoughts.js"; // Get all thoughts export const getAllThoughts = (req, res) => { diff --git a/middleware/auth.js b/middleware/auth.js new file mode 100644 index 0000000..e69de29 diff --git a/utils/saveThoughts.js b/utils/saveThoughts.js new file mode 100644 index 0000000..be775d5 --- /dev/null +++ b/utils/saveThoughts.js @@ -0,0 +1,7 @@ +import fs from "fs"; + +const saveThoughts = (thoughts) => { + fs.writeFileSync("data/data.json", JSON.stringify(thoughts, null, 2)); +}; + +export default saveThoughts; From 52659b067b503e47441d317506dd142bfbb4e432 Mon Sep 17 00:00:00 2001 From: tildetilde Date: Tue, 3 Jun 2025 16:17:55 +0200 Subject: [PATCH 08/22] Created ThoughtsModel --- TODO.md | 2 +- controllers/thoughtsController.js | 2 -- models/Thought.js | 22 ++++++++++++++++++++++ utils/seedThoughts.js | 0 4 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 models/Thought.js create mode 100644 utils/seedThoughts.js diff --git a/TODO.md b/TODO.md index 5b63d5c..56998f5 100644 --- a/TODO.md +++ b/TODO.md @@ -2,7 +2,7 @@ ## πŸ₯‡ Step 1: Finalize backend using MongoDB & Mongoose -- [ ] Create a `Thought` model in `models/Thought.js` +- [x] Create a `Thought` model in `models/Thought.js` - Fields: `message` (string, required, min/max length), `hearts`, `createdAt` - [ ] Seed the database with sample thoughts - [ ] Create route: `GET /thoughts` β†’ return latest (e.g. 20) diff --git a/controllers/thoughtsController.js b/controllers/thoughtsController.js index fcb76ce..bbe99e9 100644 --- a/controllers/thoughtsController.js +++ b/controllers/thoughtsController.js @@ -1,5 +1,3 @@ -import { v4 as uuidv4 } from "uuid"; - // controllers/thoughtsController.js import loadThoughts from "../utils/loadThoughts.js"; import saveThoughts from "../utils/saveThoughts.js"; diff --git a/models/Thought.js b/models/Thought.js new file mode 100644 index 0000000..3b3d231 --- /dev/null +++ b/models/Thought.js @@ -0,0 +1,22 @@ +import mongoose from "mongoose"; + +const ThoughtSchema = new mongoose.Schema({ + message: { + type: String, + required: [true, "A message is required"], + minlength: [5, "A message must be at least 5 characters long"], + maxlength: [140, "A message can't be longer than 140 characters"], + trim: true, + }, + hearts: { + type: Number, + default: 0, + min: [0, "Hearts cannot be negative"], + }, + createdAt: { + type: Date, + default: Date.now, + }, +}); + +export const Thought = mongoose.model("Thought", ThoughtSchema); diff --git a/utils/seedThoughts.js b/utils/seedThoughts.js new file mode 100644 index 0000000..e69de29 From 53cebc75d64eb7495d085cea44a59a7dc7774ef3 Mon Sep 17 00:00:00 2001 From: tildetilde Date: Tue, 3 Jun 2025 16:35:23 +0200 Subject: [PATCH 09/22] Seeding the data --- package.json | 1 + utils/seedThoughts.js | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/package.json b/package.json index 00addae..70ca4d2 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", "cors": "^2.8.5", + "dotenv": "^16.5.0", "express": "^4.17.3", "express-list-endpoints": "^7.1.1", "nodemon": "^3.0.1" diff --git a/utils/seedThoughts.js b/utils/seedThoughts.js index e69de29..0fd37d5 100644 --- a/utils/seedThoughts.js +++ b/utils/seedThoughts.js @@ -0,0 +1,26 @@ +import mongoose from "mongoose"; +import dotenv from "dotenv"; +import fs from "fs"; +import { Thought } from "../models/Thought.js"; + +dotenv.config(); + +const mongoUrl = + process.env.MONGO_URL || "mongodb://localhost:27017/happyThoughts"; +mongoose.connect(mongoUrl); + +const thoughtData = JSON.parse(fs.readFileSync("./data/data.json")); + +const seedDatabase = async () => { + try { + await Thought.deleteMany(); + await Thought.insertMany(thoughtData); + console.log(`🌱 Successfully seeded ${thoughtData.length} thoughts!`); + } catch (err) { + console.error("❌ Seeding error:", err); + } finally { + mongoose.disconnect(); + } +}; + +seedDatabase(); From fd8e9cfb140c67f9d42c1fe5f2e3d46d02bd351d Mon Sep 17 00:00:00 2001 From: tildetilde Date: Tue, 3 Jun 2025 17:13:39 +0200 Subject: [PATCH 10/22] Added GET route --- TODO.md | 4 +- controllers/thoughtsController.js | 106 +++++++++++++----------------- server.js | 14 ++++ utils/seedThoughts.js | 7 +- 4 files changed, 67 insertions(+), 64 deletions(-) diff --git a/TODO.md b/TODO.md index 56998f5..412ca41 100644 --- a/TODO.md +++ b/TODO.md @@ -4,8 +4,8 @@ - [x] Create a `Thought` model in `models/Thought.js` - Fields: `message` (string, required, min/max length), `hearts`, `createdAt` -- [ ] Seed the database with sample thoughts -- [ ] Create route: `GET /thoughts` β†’ return latest (e.g. 20) +- [x] Seed the database with sample thoughts +- [x] Create route: `GET /thoughts` β†’ return latest (e.g. 20) - [ ] Create route: `POST /thoughts` β†’ save new thought - [ ] Create route: `PATCH /thoughts/:id/like` β†’ increment hearts - [ ] Create route: `DELETE /thoughts/:id` β†’ delete a thought diff --git a/controllers/thoughtsController.js b/controllers/thoughtsController.js index bbe99e9..0788bb8 100644 --- a/controllers/thoughtsController.js +++ b/controllers/thoughtsController.js @@ -1,69 +1,57 @@ // controllers/thoughtsController.js -import loadThoughts from "../utils/loadThoughts.js"; -import saveThoughts from "../utils/saveThoughts.js"; - -// Get all thoughts -export const getAllThoughts = (req, res) => { - const thoughts = loadThoughts(); - let result = [...thoughts]; - - if (req.query.minHearts) { - const minHearts = parseInt(req.query.minHearts); - result = result.filter((t) => t.hearts >= minHearts); +import { Thought } from "../models/Thought.js"; + +// GET /thoughts +export const getAllThoughts = async (req, res) => { + const { + minHearts, + after, + sortBy = "createdAt", + order = "desc", + page = 1, + limit = 10, + } = req.query; + + const query = {}; + if (minHearts) { + query.hearts = { $gte: parseInt(minHearts) }; } - - if (req.query.after) { - const afterDate = new Date(req.query.after); - result = result.filter((t) => new Date(t.createdAt) > afterDate); + if (after) { + query.createdAt = { $gt: new Date(after) }; } - if (req.query.sortBy) { - const sortBy = req.query.sortBy; - const order = req.query.order === "desc" ? -1 : 1; - - const validSortFields = ["hearts", "createdAt"]; - if (!validSortFields.includes(sortBy)) { - return res.status(400).json({ - success: false, - message: "Invalid sort field", - }); - } - - result.sort((a, b) => { - if (sortBy === "hearts") { - return (a.hearts - b.hearts) * order; - } else if (sortBy === "createdAt") { - return (new Date(a.createdAt) - new Date(b.createdAt)) * order; - } - return 0; + const sortOrder = order === "desc" ? -1 : 1; + const skip = (parseInt(page) - 1) * parseInt(limit); + + try { + const totalThoughts = await Thought.countDocuments(query); + const thoughts = await Thought.find(query) + .sort({ [sortBy]: sortOrder }) + .skip(skip) + .limit(parseInt(limit)); + + res.json({ + totalThoughts, + totalPages: Math.ceil(totalThoughts / limit), + currentPage: parseInt(page), + thoughts, + }); + } catch (err) { + res.status(500).json({ + success: false, + message: "Failed to get thoughts", + error: err.message, }); } - - const page = parseInt(req.query.page) || 1; - const limit = parseInt(req.query.limit) || 10; - - const startIndex = (page - 1) * limit; - const endIndex = startIndex + limit; - - const paginatedThoughts = result.slice(startIndex, endIndex); - - res.json({ - totalThoughts: result.length, - totalPages: Math.ceil(result.length / limit), - currentPage: page, - thoughts: paginatedThoughts, - }); }; -// Get a single thought by ID -export const getThoughtById = (req, res) => { - const { id } = req.params; - const thoughts = loadThoughts(); - const thought = thoughts.find((t) => t._id === id); - - if (!thought) { - return res.status(404).json({ message: "Thought not found" }); +// GET /thoughts/:id +export const getThoughtById = async (req, res) => { + try { + const thought = await Thought.findById(req.params.id); + if (!thought) return res.status(404).json({ message: "Thought not found" }); + res.json(thought); + } catch (err) { + res.status(400).json({ message: "Invalid ID", error: err.message }); } - - res.json(thought); }; diff --git a/server.js b/server.js index 96e59d4..c541ce0 100644 --- a/server.js +++ b/server.js @@ -1,12 +1,26 @@ import cors from "cors"; import express from "express"; import fs from "fs"; +import mongoose from "mongoose"; +import dotenv from "dotenv"; import expressListEndpoints from "express-list-endpoints"; import thoughtsRoutes from "./routes/thoughts.js"; +dotenv.config(); + const port = process.env.PORT || 8080; const app = express(); +const mongoUrl = + process.env.MONGO_URL || "mongodb://localhost:27017/happyThoughts"; +mongoose + .connect(mongoUrl, { + useNewUrlParser: true, + useUnifiedTopology: true, + }) + .then(() => console.log("βœ… Connected to MongoDB")) + .catch((err) => console.error("❌ MongoDB connection error:", err)); + app.use(cors()); app.use(express.json()); diff --git a/utils/seedThoughts.js b/utils/seedThoughts.js index 0fd37d5..71e774b 100644 --- a/utils/seedThoughts.js +++ b/utils/seedThoughts.js @@ -5,9 +5,10 @@ import { Thought } from "../models/Thought.js"; dotenv.config(); -const mongoUrl = - process.env.MONGO_URL || "mongodb://localhost:27017/happyThoughts"; -mongoose.connect(mongoUrl); +const mongoUrl = mongoose + .connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true }) + .then(() => console.log("🟒 Connected to MongoDB")) + .catch((err) => console.error("πŸ”΄ MongoDB connection error:", err)); const thoughtData = JSON.parse(fs.readFileSync("./data/data.json")); From 27b50b0003907dde9f4c6a9ab619382bf253dd08 Mon Sep 17 00:00:00 2001 From: tildetilde Date: Tue, 3 Jun 2025 18:02:22 +0200 Subject: [PATCH 11/22] Added POST, PATCH, DELETE --- TODO.md | 8 ++--- controllers/thoughtsController.js | 51 +++++++++++++++++++++++++++++++ routes/thoughts.js | 12 ++++++++ 3 files changed, 67 insertions(+), 4 deletions(-) diff --git a/TODO.md b/TODO.md index 412ca41..5a7a7bb 100644 --- a/TODO.md +++ b/TODO.md @@ -6,10 +6,10 @@ - Fields: `message` (string, required, min/max length), `hearts`, `createdAt` - [x] Seed the database with sample thoughts - [x] Create route: `GET /thoughts` β†’ return latest (e.g. 20) -- [ ] Create route: `POST /thoughts` β†’ save new thought -- [ ] Create route: `PATCH /thoughts/:id/like` β†’ increment hearts -- [ ] Create route: `DELETE /thoughts/:id` β†’ delete a thought -- [ ] BONUS: `GET /thoughts?page=2` β†’ use `.skip().limit()` for pagination +- [x] Create route: `POST /thoughts` β†’ save new thought +- [x] Create route: `PATCH /thoughts/:id/like` β†’ increment hearts +- [x] Create route: `DELETE /thoughts/:id` β†’ delete a thought +- [x] BONUS: `GET /thoughts?page=2` β†’ use `.skip().limit()` for pagination - [ ] BONUS: `GET /thoughts?category=joy` β†’ filter with query params --- diff --git a/controllers/thoughtsController.js b/controllers/thoughtsController.js index 0788bb8..926f3fa 100644 --- a/controllers/thoughtsController.js +++ b/controllers/thoughtsController.js @@ -55,3 +55,54 @@ export const getThoughtById = async (req, res) => { res.status(400).json({ message: "Invalid ID", error: err.message }); } }; + +// POST /thoughts +export const createThought = async (req, res) => { + const { message } = req.body; + + try { + const newThought = new Thought({ message }); + const saved = await newThought.save(); + res.status(201).json(saved); + } catch (err) { + res.status(400).json({ + success: false, + message: "Failed to create thought", + error: err.message, + }); + } +}; + +// PATCH /thoughts/:id/like +export const likeThought = async (req, res) => { + try { + const updated = await Thought.findByIdAndUpdate( + req.params.id, + { $inc: { hearts: 1 } }, + { new: true } + ); + if (!updated) return res.status(404).json({ message: "Thought not found" }); + res.json(updated); + } catch (err) { + res.status(400).json({ + success: false, + message: "Failed to like thought", + error: err.message, + }); + } +}; + +// DELETE /thoughts/:id +export const deleteThought = async (req, res) => { + try { + const deleted = await Thought.findByIdAndDelete(req.params.id); + if (!deleted) return res.status(404).json({ message: "Thought not found" }); + res.json({ success: true, message: "Thought deleted", id: req.params.id }); + } catch (err) { + res.status(400).json({ + success: false, + message: "Failed to delete thought", + error: err.message, + }); + } +}; diff --git a/routes/thoughts.js b/routes/thoughts.js index 331bd84..6bd37e0 100644 --- a/routes/thoughts.js +++ b/routes/thoughts.js @@ -3,6 +3,9 @@ import express from "express"; import { getAllThoughts, getThoughtById, + createThought, + likeThought, + deleteThought, } from "../controllers/thoughtsController.js"; const router = express.Router(); @@ -13,4 +16,13 @@ router.get("/", getAllThoughts); // GET /thoughts/:id – Get one thought router.get("/:id", getThoughtById); +// POST /thoughts – Create a new thought +router.post("/", createThought); + +// PATCH /thoughts/:id/like – Like a thought +router.patch("/:id/like", likeThought); + +// DELETE /thoughts/:id – Delete a thought +router.delete("/:id", deleteThought); + export default router; From 0efc2c2dc83eeab8a95e468376b3fd554adcf36b Mon Sep 17 00:00:00 2001 From: tildetilde Date: Wed, 4 Jun 2025 12:25:45 +0200 Subject: [PATCH 12/22] Add mongoose to dependencies --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 70ca4d2..d320bcb 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "dotenv": "^16.5.0", "express": "^4.17.3", "express-list-endpoints": "^7.1.1", + "mongoose": "^8.15.1", "nodemon": "^3.0.1" } } From 1de7a82e897e150a46294e35dded8ee6926f386c Mon Sep 17 00:00:00 2001 From: tildetilde Date: Wed, 4 Jun 2025 14:19:18 +0200 Subject: [PATCH 13/22] Update server.js --- server.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/server.js b/server.js index c541ce0..a3b064f 100644 --- a/server.js +++ b/server.js @@ -14,10 +14,7 @@ const app = express(); const mongoUrl = process.env.MONGO_URL || "mongodb://localhost:27017/happyThoughts"; mongoose - .connect(mongoUrl, { - useNewUrlParser: true, - useUnifiedTopology: true, - }) + .connect(mongoUrl) .then(() => console.log("βœ… Connected to MongoDB")) .catch((err) => console.error("❌ MongoDB connection error:", err)); From 4c1805f70560e86fe43767025177c9d2a8629367 Mon Sep 17 00:00:00 2001 From: tildetilde Date: Wed, 4 Jun 2025 15:14:02 +0200 Subject: [PATCH 14/22] Update TODO.md --- TODO.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/TODO.md b/TODO.md index 5a7a7bb..e3ae4ee 100644 --- a/TODO.md +++ b/TODO.md @@ -3,7 +3,10 @@ ## πŸ₯‡ Step 1: Finalize backend using MongoDB & Mongoose - [x] Create a `Thought` model in `models/Thought.js` - - Fields: `message` (string, required, min/max length), `hearts`, `createdAt` + - Fields: + []`message` (string, required, min/max length), + []`hearts`, + []`createdAt` - [x] Seed the database with sample thoughts - [x] Create route: `GET /thoughts` β†’ return latest (e.g. 20) - [x] Create route: `POST /thoughts` β†’ save new thought @@ -17,7 +20,7 @@ ## πŸ₯ˆ Step 2: Add validation & error handling - [ ] Add Mongoose validation in the model (e.g. min/max message length) -- [ ] Use `try/catch` in all routes +- [x] Use `try/catch` in all routes - [ ] Return proper status codes (400, 404, 500) with `.status().json() - [ ] Return useful error messages for the frontend @@ -25,23 +28,20 @@ ## πŸ₯‰ Step 3: Connect frontend to the API -- [ ] Change API URL in `happy-thoughts` frontend - ```js - const API_URL = "http://localhost:8080/thoughts"; // or deployed URL - ``` +- [x] Change API URL in `happy-thoughts` frontend - Update fetch requests: -- [ ] GET /thoughts β†’ display the thought list -- [ ] POST /thoughts β†’ send a new thought -- [ ] PATCH /thoughts/:id/like β†’ like a thought -- [ ] DELETE /thoughts/:id β†’ delete a thought -- [ ] Show errors and loading states in the UI +- [x] GET /thoughts β†’ display the thought list +- [x] POST /thoughts β†’ send a new thought +- [x] PATCH /thoughts/:id/like β†’ like a thought +- [x] DELETE /thoughts/:id β†’ delete a thought +- [x] Show errors and loading states in the UI ## 🏁 Step 4: Deploy & manage environments -- [ ] Deploy backend to Render -- [ ] Deploy database to MongoDB Atlas -- [ ] Add .env on Render with MONGO_URL -- [ ] Update the frontend to point to deployed backend +- [x] Deploy backend to Render +- [x] Deploy database to MongoDB Atlas +- [x] Add .env on Render with MONGO_URL +- [x] Update the frontend to point to deployed backend ## 🌈 Step 5: Stretch goals – once core functionality is working From ee29461ef168a5ef343950e8c0a42271cb1a4662 Mon Sep 17 00:00:00 2001 From: tildetilde Date: Wed, 4 Jun 2025 15:59:34 +0200 Subject: [PATCH 15/22] Update thought --- TODO.md | 13 +++++++------ controllers/thoughtsController.js | 32 +++++++++++++++++++++++++++++++ routes/thoughts.js | 4 ++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/TODO.md b/TODO.md index e3ae4ee..80c190e 100644 --- a/TODO.md +++ b/TODO.md @@ -4,9 +4,10 @@ - [x] Create a `Thought` model in `models/Thought.js` - Fields: - []`message` (string, required, min/max length), - []`hearts`, - []`createdAt` + [x]`message` (string, required, min/max length), + [x]`hearts`, + [x]`createdAt` +- [ ] Add update thought in frontend and backend - [x] Seed the database with sample thoughts - [x] Create route: `GET /thoughts` β†’ return latest (e.g. 20) - [x] Create route: `POST /thoughts` β†’ save new thought @@ -19,10 +20,10 @@ ## πŸ₯ˆ Step 2: Add validation & error handling -- [ ] Add Mongoose validation in the model (e.g. min/max message length) +- [x] Add Mongoose validation in the model (e.g. min/max message length) - [x] Use `try/catch` in all routes -- [ ] Return proper status codes (400, 404, 500) with `.status().json() -- [ ] Return useful error messages for the frontend +- [x] Return proper status codes (400, 404, 500) with `.status().json() +- [x] Return useful error messages for the frontend --- diff --git a/controllers/thoughtsController.js b/controllers/thoughtsController.js index 926f3fa..2b2e47c 100644 --- a/controllers/thoughtsController.js +++ b/controllers/thoughtsController.js @@ -92,6 +92,38 @@ export const likeThought = async (req, res) => { } }; +// PATCH /thoughts/:id – Update the message of a thought +export const updateThought = async (req, res) => { + const { id } = req.params; + const { message } = req.body; + + try { + const thought = await Thought.findById(id); + if (!thought) { + return res + .status(404) + .json({ success: false, message: "Thought not found" }); + } + + if (!message || message.length < 5 || message.length > 140) { + return res + .status(400) + .json({ success: false, message: "Invalid message length" }); + } + + thought.message = message; + await thought.save(); + + res.status(200).json({ success: true, updatedThought: thought }); + } catch (err) { + res.status(500).json({ + success: false, + message: "Could not update thought", + error: err.message, + }); + } +}; + // DELETE /thoughts/:id export const deleteThought = async (req, res) => { try { diff --git a/routes/thoughts.js b/routes/thoughts.js index 6bd37e0..2ed2429 100644 --- a/routes/thoughts.js +++ b/routes/thoughts.js @@ -6,6 +6,7 @@ import { createThought, likeThought, deleteThought, + updateThought, } from "../controllers/thoughtsController.js"; const router = express.Router(); @@ -22,6 +23,9 @@ router.post("/", createThought); // PATCH /thoughts/:id/like – Like a thought router.patch("/:id/like", likeThought); +// PATCH /thoughts/:id – Update a thought +router.patch("/:id", updateThought); + // DELETE /thoughts/:id – Delete a thought router.delete("/:id", deleteThought); From bb01e32172f84ef5d4a7122e3df90b5ad6d6082f Mon Sep 17 00:00:00 2001 From: tildetilde Date: Wed, 4 Jun 2025 16:01:24 +0200 Subject: [PATCH 16/22] Updated update thoughts --- controllers/thoughtsController.js | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/controllers/thoughtsController.js b/controllers/thoughtsController.js index 2b2e47c..e12da98 100644 --- a/controllers/thoughtsController.js +++ b/controllers/thoughtsController.js @@ -94,31 +94,20 @@ export const likeThought = async (req, res) => { // PATCH /thoughts/:id – Update the message of a thought export const updateThought = async (req, res) => { - const { id } = req.params; const { message } = req.body; try { - const thought = await Thought.findById(id); - if (!thought) { - return res - .status(404) - .json({ success: false, message: "Thought not found" }); - } - - if (!message || message.length < 5 || message.length > 140) { - return res - .status(400) - .json({ success: false, message: "Invalid message length" }); - } - - thought.message = message; - await thought.save(); - - res.status(200).json({ success: true, updatedThought: thought }); + const updated = await Thought.findByIdAndUpdate( + req.params.id, + { message }, + { new: true } + ); + if (!updated) return res.status(404).json({ message: "Thought not found" }); + res.json(updated); } catch (err) { - res.status(500).json({ + res.status(400).json({ success: false, - message: "Could not update thought", + message: "Failed to update thought", error: err.message, }); } From ecb59686b9372e0f9d4bdcc72341635376a7da98 Mon Sep 17 00:00:00 2001 From: tildetilde Date: Thu, 5 Jun 2025 10:43:40 +0200 Subject: [PATCH 17/22] Update server.js --- server.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index a3b064f..6c9651c 100644 --- a/server.js +++ b/server.js @@ -18,7 +18,11 @@ mongoose .then(() => console.log("βœ… Connected to MongoDB")) .catch((err) => console.error("❌ MongoDB connection error:", err)); -app.use(cors()); +app.use( + cors({ + methods: ["GET", "POST", "PATCH", "DELETE"], + }) +); app.use(express.json()); app.get("/", (req, res) => { From 5021a40b8c3a4235d311e04623c95c178d3ca83d Mon Sep 17 00:00:00 2001 From: tildetilde Date: Thu, 12 Jun 2025 11:24:25 +0200 Subject: [PATCH 18/22] Implemented authentication, login and register --- controllers/thoughtsController.js | 45 ++++++++++++++++++------ middleware/auth.js | 21 ++++++++++++ models/Thought.js | 5 +++ models/User.js | 14 ++++++++ package.json | 2 ++ routes/thoughts.js | 7 ++-- routes/users.js | 57 +++++++++++++++++++++++++++++++ server.js | 5 ++- 8 files changed, 142 insertions(+), 14 deletions(-) create mode 100644 models/User.js create mode 100644 routes/users.js diff --git a/controllers/thoughtsController.js b/controllers/thoughtsController.js index e12da98..be00b1a 100644 --- a/controllers/thoughtsController.js +++ b/controllers/thoughtsController.js @@ -61,7 +61,10 @@ export const createThought = async (req, res) => { const { message } = req.body; try { - const newThought = new Thought({ message }); + const newThought = new Thought({ + message, + createdBy: req.user.id, + }); const saved = await newThought.save(); res.status(201).json(saved); } catch (err) { @@ -94,15 +97,24 @@ export const likeThought = async (req, res) => { // PATCH /thoughts/:id – Update the message of a thought export const updateThought = async (req, res) => { + const { id } = req.params; const { message } = req.body; try { - const updated = await Thought.findByIdAndUpdate( - req.params.id, - { message }, - { new: true } - ); - if (!updated) return res.status(404).json({ message: "Thought not found" }); + const thought = await Thought.findById(id); + + if (!thought) { + return res.status(404).json({ message: "Thought not found" }); + } + + if (thought.createdBy.toString() !== req.user.id) { + return res + .status(403) + .json({ message: "Not authorized to update this thought" }); + } + + thought.message = message || thought.message; + const updated = await thought.save(); res.json(updated); } catch (err) { res.status(400).json({ @@ -115,10 +127,23 @@ export const updateThought = async (req, res) => { // DELETE /thoughts/:id export const deleteThought = async (req, res) => { + const { id } = req.params; + try { - const deleted = await Thought.findByIdAndDelete(req.params.id); - if (!deleted) return res.status(404).json({ message: "Thought not found" }); - res.json({ success: true, message: "Thought deleted", id: req.params.id }); + const thought = await Thought.findById(id); + + if (!thought) { + return res.status(404).json({ message: "Thought not found" }); + } + + if (thought.createdBy.toString() !== req.user.id) { + return res + .status(403) + .json({ message: "Not authorized to delete this thought" }); + } + + await thought.deleteOne(); + res.json({ success: true, message: "Thought deleted", id }); } catch (err) { res.status(400).json({ success: false, diff --git a/middleware/auth.js b/middleware/auth.js index e69de29..d3b5043 100644 --- a/middleware/auth.js +++ b/middleware/auth.js @@ -0,0 +1,21 @@ +import jwt from "jsonwebtoken"; + +const JWT_SECRET = process.env.JWT_SECRET || "secret-key"; + +export const authenticateUser = (req, res, next) => { + const authHeader = req.headers.authorization; + + if (!authHeader) { + return res.status(401).json({ message: "Access token missing" }); + } + + const token = authHeader.replace("Bearer ", ""); + + try { + const decoded = jwt.verify(token, JWT_SECRET); + req.user = decoded; // du kan nu anvΓ€nda req.user.id i skyddade routes + next(); + } catch (err) { + res.status(401).json({ message: "Invalid token" }); + } +}; diff --git a/models/Thought.js b/models/Thought.js index 3b3d231..88a9dac 100644 --- a/models/Thought.js +++ b/models/Thought.js @@ -17,6 +17,11 @@ const ThoughtSchema = new mongoose.Schema({ type: Date, default: Date.now, }, + createdBy: { + type: mongoose.Schema.Types.ObjectId, + ref: "User", + required: true, + }, }); export const Thought = mongoose.model("Thought", ThoughtSchema); diff --git a/models/User.js b/models/User.js new file mode 100644 index 0000000..40f4e7e --- /dev/null +++ b/models/User.js @@ -0,0 +1,14 @@ +import mongoose from "mongoose"; +import bcrypt from "bcryptjs"; + +const UserSchema = new mongoose.Schema({ + email: { type: String, unique: true, required: true }, + password: { type: String, required: true }, + username: { type: String }, +}); + +UserSchema.pre("save", async function () { + this.password = await bcrypt.hash(this.password, 10); +}); + +export const User = mongoose.model("User", UserSchema); diff --git a/package.json b/package.json index d320bcb..ba20898 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", + "bcryptjs": "^3.0.2", "cors": "^2.8.5", "dotenv": "^16.5.0", "express": "^4.17.3", "express-list-endpoints": "^7.1.1", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.15.1", "nodemon": "^3.0.1" } diff --git a/routes/thoughts.js b/routes/thoughts.js index 2ed2429..7f421b1 100644 --- a/routes/thoughts.js +++ b/routes/thoughts.js @@ -1,5 +1,6 @@ // routes/thoughts.js import express from "express"; +import { authenticateUser } from "../middleware/auth.js"; import { getAllThoughts, getThoughtById, @@ -18,15 +19,15 @@ router.get("/", getAllThoughts); router.get("/:id", getThoughtById); // POST /thoughts – Create a new thought -router.post("/", createThought); +router.post("/", authenticateUser, createThought); // PATCH /thoughts/:id/like – Like a thought router.patch("/:id/like", likeThought); // PATCH /thoughts/:id – Update a thought -router.patch("/:id", updateThought); +router.patch("/:id", authenticateUser, updateThought); // DELETE /thoughts/:id – Delete a thought -router.delete("/:id", deleteThought); +router.delete("/:id", authenticateUser, deleteThought); export default router; diff --git a/routes/users.js b/routes/users.js new file mode 100644 index 0000000..da99460 --- /dev/null +++ b/routes/users.js @@ -0,0 +1,57 @@ +import express from "express"; +import bcrypt from "bcryptjs"; +import jwt from "jsonwebtoken"; +import { User } from "../models/User.js"; + +const router = express.Router(); + +const JWT_SECRET = process.env.JWT_SECRET || "secret-key"; + +// POST /register +router.post("/register", async (req, res) => { + const { email, password, username } = req.body; + + if (!email || !password) { + return res.status(400).json({ message: "Email and password are required" }); + } + + try { + const existingUser = await User.findOne({ email }); + if (existingUser) { + return res + .status(400) + .json({ message: "That email address already exists" }); + } + + const user = await new User({ email, password, username }).save(); + res.status(201).json({ id: user._id, email: user.email }); + } catch (err) { + res + .status(500) + .json({ message: "Something went wrong", error: err.message }); + } +}); + +// POST /login +router.post("/login", async (req, res) => { + const { email, password } = req.body; + + try { + const user = await User.findOne({ email }); + if (!user) { + return res.status(401).json({ message: "Invalid email or password" }); + } + + const isPasswordCorrect = await bcrypt.compare(password, user.password); + if (!isPasswordCorrect) { + return res.status(401).json({ message: "Invalid email or password" }); + } + + const token = jwt.sign({ id: user._id }, JWT_SECRET, { expiresIn: "24h" }); + res.status(200).json({ token, id: user._id, email: user.email }); + } catch (err) { + res.status(500).json({ message: "Login failed", error: err.message }); + } +}); + +export default router; diff --git a/server.js b/server.js index 6c9651c..3d43da2 100644 --- a/server.js +++ b/server.js @@ -5,10 +5,11 @@ import mongoose from "mongoose"; import dotenv from "dotenv"; import expressListEndpoints from "express-list-endpoints"; import thoughtsRoutes from "./routes/thoughts.js"; +import usersRoutes from "./routes/users.js"; dotenv.config(); -const port = process.env.PORT || 8080; +const port = process.env.PORT || 8082; const app = express(); const mongoUrl = @@ -35,6 +36,8 @@ app.get("/", (req, res) => { app.use("/thoughts", thoughtsRoutes); +app.use("/users", usersRoutes); + app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); From ce625de769b9bef32aed45183f8332a185c84e1c Mon Sep 17 00:00:00 2001 From: tildetilde Date: Thu, 12 Jun 2025 12:27:01 +0200 Subject: [PATCH 19/22] Authentication for signup --- routes/users.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/routes/users.js b/routes/users.js index da99460..9729697 100644 --- a/routes/users.js +++ b/routes/users.js @@ -25,6 +25,10 @@ router.post("/register", async (req, res) => { const user = await new User({ email, password, username }).save(); res.status(201).json({ id: user._id, email: user.email }); + + const token = jwt.sign({ id: user._id }, JWT_SECRET, { expiresIn: "24h" }); + + res.status(201).json({ token, id: user._id, email: user.email }); } catch (err) { res .status(500) From 3e3823a510025e6d762d33bacfb071fb3a17ffe5 Mon Sep 17 00:00:00 2001 From: tildetilde Date: Thu, 12 Jun 2025 12:54:34 +0200 Subject: [PATCH 20/22] Update users.js --- routes/users.js | 1 - 1 file changed, 1 deletion(-) diff --git a/routes/users.js b/routes/users.js index 9729697..4ea91c3 100644 --- a/routes/users.js +++ b/routes/users.js @@ -24,7 +24,6 @@ router.post("/register", async (req, res) => { } const user = await new User({ email, password, username }).save(); - res.status(201).json({ id: user._id, email: user.email }); const token = jwt.sign({ id: user._id }, JWT_SECRET, { expiresIn: "24h" }); From 3509abf81c5a107787ecccc8ff2e43938bfebad7 Mon Sep 17 00:00:00 2001 From: tildetilde Date: Fri, 13 Jun 2025 11:01:48 +0200 Subject: [PATCH 21/22] regex validation --- models/User.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/models/User.js b/models/User.js index 40f4e7e..bbc9525 100644 --- a/models/User.js +++ b/models/User.js @@ -2,7 +2,15 @@ import mongoose from "mongoose"; import bcrypt from "bcryptjs"; const UserSchema = new mongoose.Schema({ - email: { type: String, unique: true, required: true }, + email: { + type: String, + required: true, + unique: true, + lowercase: true, + trim: true, + match: [/^\S+@\S+\.\S+$/, "Please use a valid email address"], + }, + password: { type: String, required: true }, username: { type: String }, }); From c18908272011757759dd49fcd734ebf61e6b13d2 Mon Sep 17 00:00:00 2001 From: tildetilde Date: Fri, 13 Jun 2025 12:01:16 +0200 Subject: [PATCH 22/22] Updated user --- models/User.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/models/User.js b/models/User.js index bbc9525..64ba060 100644 --- a/models/User.js +++ b/models/User.js @@ -6,9 +6,6 @@ const UserSchema = new mongoose.Schema({ type: String, required: true, unique: true, - lowercase: true, - trim: true, - match: [/^\S+@\S+\.\S+$/, "Please use a valid email address"], }, password: { type: String, required: true },