From a8a83899e6bfd3c14d7a8acc1149d6d066df3430 Mon Sep 17 00:00:00 2001 From: "DANUSHKA\\danus" Date: Tue, 17 Jun 2025 09:00:34 +0200 Subject: [PATCH 01/15] file initiated --- data.json | 126 ++++++--------------------------------------------- package.json | 7 ++- server.js | 32 ++++++++----- 3 files changed, 40 insertions(+), 125 deletions(-) diff --git a/data.json b/data.json index a2c844f..ce436ec 100644 --- a/data.json +++ b/data.json @@ -1,121 +1,23 @@ [ - { - "_id": "682bab8c12155b00101732ce", - "message": "Berlin baby", - "hearts": 37, - "createdAt": "2025-05-19T22:07:08.999Z", - "__v": 0 - }, - { - "_id": "682e53cc4fddf50010bbe739", - "message": "My family!", - "hearts": 0, - "createdAt": "2025-05-22T22:29:32.232Z", - "__v": 0 - }, - { - "_id": "682e4f844fddf50010bbe738", - "message": "The smell of coffee in the morning....", - "hearts": 23, - "createdAt": "2025-05-22T22:11:16.075Z", - "__v": 0 - }, - { - "_id": "682e48bf4fddf50010bbe737", - "message": "Newly washed bedlinen, kids that sleeps through the night.. FINGERS CROSSED ๐Ÿคž๐Ÿผ\n", - "hearts": 6, - "createdAt": "2025-05-21T21:42:23.862Z", - "__v": 0 - }, - { - "_id": "682e45804fddf50010bbe736", - "message": "I am happy that I feel healthy and have energy again", - "hearts": 13, - "createdAt": "2025-05-21T21:28:32.196Z", - "__v": 0 - }, - { - "_id": "682e23fecf615800105107aa", - "message": "cold beer", - "hearts": 2, - "createdAt": "2025-05-21T19:05:34.113Z", - "__v": 0 - }, - { - "_id": "682e22aecf615800105107a9", - "message": "My friend is visiting this weekend! <3", - "hearts": 6, - "createdAt": "2025-05-21T18:59:58.121Z", - "__v": 0 - }, - { - "_id": "682cec1b17487d0010a298b6", - "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 - }, { - "_id": "682cebbe17487d0010a298b5", - "message": "Tacos and tequila๐ŸŒฎ๐Ÿน", - "hearts": 2, - "createdAt": "2025-05-19T20:53:18.899Z", - "__v": 0 + "id": 1, + "message": "Today is a beautiful day! ๐ŸŒž", + "hearts": 5, + "createdAt": "2025-06-15T10:00:00Z", + "category": "Life" }, { - "_id": "682ceb5617487d0010a298b4", - "message": "Netflix and late night ice-cream๐Ÿฆ", - "hearts": 1, - "createdAt": "2025-05-18T20:51:34.494Z", - "__v": 0 + "id": 2, + "message": "I made progress on my project!", + "hearts": 10, + "createdAt": "2025-06-14T15:30:00Z", + "category": "Work" }, { - "_id": "682c99ba3bff2d0010f5d44e", - "message": "Summer is coming...", + "id": 3, + "message": "I had the best ramen today ๐Ÿœ", "hearts": 2, - "createdAt": "2025-05-20T15:03:22.379Z", - "__v": 0 - }, - { - "_id": "682c706c951f7a0017130024", - "message": "Exercise? I thought you said extra fries! ๐ŸŸ๐Ÿ˜‚", - "hearts": 14, - "createdAt": "2025-05-20T12:07:08.185Z", - "__v": 0 - }, - { - "_id": "682c6fe1951f7a0017130023", - "message": "Iโ€™m on a seafood diet. I see food, and I eat it.", - "hearts": 4, - "createdAt": "2025-05-20T12:04:49.978Z", - "__v": 0 - }, - { - "_id": "682c6f0e951f7a0017130022", - "message": "Cute monkeys๐Ÿ’", - "hearts": 2, - "createdAt": "2025-05-20T12:01:18.308Z", - "__v": 0 - }, - { - "_id": "682c6e65951f7a0017130021", - "message": "The weather is nice!", - "hearts": 0, - "createdAt": "2025-05-20T11:58:29.662Z", - "__v": 0 - }, - { - "_id": "682bfdb4270ca300105af221", - "message": "good vibes and good things", - "hearts": 3, - "createdAt": "2025-05-20T03:57:40.322Z", - "__v": 0 - }, - { - "_id": "682bab8c12155b00101732ce", - "message": "Berlin baby", - "hearts": 37, - "createdAt": "2025-05-19T22:07:08.999Z", - "__v": 0 + "createdAt": "2025-06-13T20:00:00Z", + "category": "Food" } ] \ No newline at end of file diff --git a/package.json b/package.json index bf25bb6..95c8f35 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,10 @@ "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", "cors": "^2.8.5", - "express": "^4.17.3", - "nodemon": "^3.0.1" + "e": "^0.2.33", + "express": "^4.21.2", + "express-list-endpoints": "^7.1.1", + "nodemon": "^3.0.1", + "xpress": "^2.4.6" } } diff --git a/server.js b/server.js index f47771b..a0bc419 100644 --- a/server.js +++ b/server.js @@ -1,22 +1,32 @@ -import cors from "cors" -import express from "express" +import cors from "cors"; +import express from "express"; // 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 +let thoughts = JSON.parse(fs.readFileSync('./data.json')); + +// Start defining routes here app.get("/", (req, res) => { - res.send("Hello Technigo!") -}) + res.send("Welcome to the Happy Thoughts API ๐Ÿ’ฌ"); +}); + +app.get("/thoughts", (req, res) => { + res.json([ + { id: 1, name: "Alice" }, + { id: 2, name: "Bob" }, + { id: 3, name: "Charlie" }, + ]); +}); // Start the server app.listen(port, () => { - console.log(`Server running on http://localhost:${port}`) -}) + console.log(`Server running on http://localhost:${port}`); +}); From de66fd7e485b886c70fe79e944485aec60db4c07 Mon Sep 17 00:00:00 2001 From: "DANUSHKA\\danus" Date: Mon, 4 Aug 2025 12:36:32 +0200 Subject: [PATCH 02/15] server.js initiatted --- data.json | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- server.js | 37 ++++++++++++++++++++++++++----- 2 files changed, 96 insertions(+), 6 deletions(-) diff --git a/data.json b/data.json index ce436ec..fe3771d 100644 --- a/data.json +++ b/data.json @@ -19,5 +19,68 @@ "hearts": 2, "createdAt": "2025-06-13T20:00:00Z", "category": "Food" + }, + { + "id": 4, + "message": "I made progress on my project!", + "hearts": 10, + "createdAt": "2025-06-14T15:30:00Z", + "category": "Work" + }, + { + "id": 5, + "message": "I had the best ramen today ๐Ÿœ", + "hearts": 2, + "createdAt": "2025-06-13T20:00:00Z", + "category": "Food" + }, + { + "id": 6, + "message": "I learned something new today! ๐Ÿ“š", + "hearts": 8, + "createdAt": "2025-06-12T12:00:00Z", + "category": "Education" + }, + { + "id": 7, + "message": "I went for a long walk in the park ๐ŸŒณ", + "hearts": 3, + "createdAt": "2025-06-11T08:30:00Z", + "category": "Health" + }, + { + "id": 8, + "message": "I had a great time with friends last night! ๐ŸŽ‰", + "hearts": 12, + "createdAt": "2025-06-10T22:00:00Z", + "category": "Social" + }, + { + "id": 9, + "message": "I finished reading a fantastic book ๐Ÿ“–", + "hearts": 6, + "createdAt": "2025-06-09T18:45:00Z", + "category": "Leisure" + }, + { + "id": 10, + "message": "I tried a new recipe and it was delicious! ๐Ÿฝ๏ธ", + "hearts": 4, + "createdAt": "2025-06-08T14:15:00Z", + "category": "Food" + }, + { + "id": 11, + "message": "I completed a challenging workout today! ๐Ÿ’ช", + "hearts": 7, + "createdAt": "2025-06-07T16:00:00Z", + "category": "Health" + }, + { + "id": 12, + "message": "I had a productive day at work! ๐Ÿ–ฅ๏ธ", + "hearts": 9, + "createdAt": "2025-06-06T11:30:00Z", + "category": "Work" } -] \ No newline at end of file +] diff --git a/server.js b/server.js index a0bc419..7ba96e5 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,7 @@ import cors from "cors"; import express from "express"; +import listEndpoints from "express-list-endpoints"; +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: @@ -19,11 +21,36 @@ app.get("/", (req, res) => { }); app.get("/thoughts", (req, res) => { - res.json([ - { id: 1, name: "Alice" }, - { id: 2, name: "Bob" }, - { id: 3, name: "Charlie" }, - ]); + let result = [...thoughts]; + const { heartsMin, category, sortBy, page, limit } = req.query; + + if (heartsMin) { + result = result.filter(t => t.hearts >= parseInt(heartsMin)); + } + + if (category) { + result = result.filter(t => t.category.toLowerCase() === category.toLowerCase()); + } + + if (sortBy === "hearts") { + result.sort((a, b) => b.hearts - a.hearts); + } else if (sortBy === "date") { + result.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); + } + + // Pagination + const pageInt = parseInt(page) || 1; + const limitInt = parseInt(limit) || result.length; + const start = (pageInt - 1) * limitInt; + const end = start + limitInt; + + const paginated = result.slice(start, end); + + res.json({ + page: pageInt, + total: result.length, + results: paginated + }); }); // Start the server From 396459b7580da09a2ea7c340d1b351cde0923cd6 Mon Sep 17 00:00:00 2001 From: "DANUSHKA\\danus" Date: Thu, 7 Aug 2025 09:24:02 +0200 Subject: [PATCH 03/15] add a route for get single thought --- server.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/server.js b/server.js index 7ba96e5..9707faa 100644 --- a/server.js +++ b/server.js @@ -53,6 +53,17 @@ app.get("/thoughts", (req, res) => { }); }); +app.get("/thoughts/:id", (req, res) => { + const { id } = req.params; + const thought = thoughts.find(t => t.id === parseInt(id)); + + if (!thought) { + return res.status(404).json({ error: "Thought not found." }); + } + + res.json(thought); +}); + // Start the server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); From b92cb97873c596977293b188bce28c0842584442 Mon Sep 17 00:00:00 2001 From: "DANUSHKA\\danus" Date: Mon, 11 Aug 2025 11:02:27 +0200 Subject: [PATCH 04/15] add listendpoints --- server.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index 9707faa..0fde690 100644 --- a/server.js +++ b/server.js @@ -13,11 +13,12 @@ const app = express(); app.use(cors()); app.use(express.json()); -let thoughts = JSON.parse(fs.readFileSync('./data.json')); +let thoughts = JSON.parse(fs.readFileSync('./data.json')); // Start defining routes here app.get("/", (req, res) => { res.send("Welcome to the Happy Thoughts API ๐Ÿ’ฌ"); + endpoints: listEndpoints(app) }); app.get("/thoughts", (req, res) => { From 6ac4e9040d07eed5a5d2d8463f89a361f0155d85 Mon Sep 17 00:00:00 2001 From: "DANUSHKA\\danus" Date: Tue, 12 Aug 2025 08:58:42 +0200 Subject: [PATCH 05/15] coonection build with mongodb --- package.json | 1 + server.js | 28 ++++++++++++++++++++++++++-- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 95c8f35..da9e6ff 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "e": "^0.2.33", "express": "^4.21.2", "express-list-endpoints": "^7.1.1", + "mongoose": "^8.17.1", "nodemon": "^3.0.1", "xpress": "^2.4.6" } diff --git a/server.js b/server.js index 0fde690..557a626 100644 --- a/server.js +++ b/server.js @@ -2,18 +2,39 @@ import cors from "cors"; import express from "express"; import listEndpoints from "express-list-endpoints"; import fs from "fs"; +import mongoose from 'mongoose'; // Defines the port the app will run on. Defaults to 8080, but can be overridden // when starting the server. Example command to overwrite PORT env variable value: // PORT=9000 npm start -const port = process.env.PORT || 8080; +const port = process.env.PORT || 80; const app = express(); // Add middlewares to enable cors and json body parsing app.use(cors()); app.use(express.json()); -let thoughts = JSON.parse(fs.readFileSync('./data.json')); +let thoughts = JSON.parse(fs.readFileSync('./data.json')); + + + + +const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/books" +mongoose.connect(mongoUrl) +mongoose.Promise = Promise + +const Author = mongoose.model('Author', { + name: String +}) + +const Book = mongoose.model('Book', { + title: String, + author: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Author' + } +}) + // Start defining routes here app.get("/", (req, res) => { @@ -69,3 +90,6 @@ app.get("/thoughts/:id", (req, res) => { app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); + + + From 1fa84c4095461cc8def2368a50435a2f69537b4d Mon Sep 17 00:00:00 2001 From: "DANUSHKA\\danus" Date: Tue, 12 Aug 2025 09:03:47 +0200 Subject: [PATCH 06/15] added mongoose schema --- server.js | 51 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/server.js b/server.js index 557a626..b44cab7 100644 --- a/server.js +++ b/server.js @@ -3,6 +3,13 @@ import express from "express"; import listEndpoints from "express-list-endpoints"; import fs from "fs"; import mongoose from 'mongoose'; +import dotenv from "dotenv"; + + +// Load environment variables from .env file +// This allows you to set environment variables like PORT and MONGO_URL +// in a .env file for local development +dotenv.config(); // 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: @@ -17,23 +24,35 @@ app.use(express.json()); let thoughts = JSON.parse(fs.readFileSync('./data.json')); - - -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/books" -mongoose.connect(mongoUrl) -mongoose.Promise = Promise - -const Author = mongoose.model('Author', { - name: String -}) - -const Book = mongoose.model('Book', { - title: String, - author: { - type: mongoose.Schema.Types.ObjectId, - ref: 'Author' +const mongoUrl = process.env.MONGO_URL || "mongodb://localhost:27017/happythoughts"; + +// Connect to MongoDB +mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true }); +mongoose.Promise = Promise; + +// ----- Mongoose Schema ----- +const thoughtSchema = new mongoose.Schema({ + message: { + type: String, + required: [true, "Message is required"], + minlength: [5, "Message must be at least 5 characters"], + maxlength: [140, "Message must be max 140 characters"] + }, + hearts: { + type: Number, + default: 0 + }, + category: { + type: String, + default: "General" + }, + createdAt: { + type: Date, + default: () => new Date() } -}) +}); + +const Thought = mongoose.model("Thought", thoughtSchema); // Start defining routes here From a2d82ba1420852c13308cf879a21d9d0382d5c1d Mon Sep 17 00:00:00 2001 From: "DANUSHKA\\danus" Date: Tue, 12 Aug 2025 09:06:55 +0200 Subject: [PATCH 07/15] add routes for get all thoughts --- server.js | 71 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/server.js b/server.js index b44cab7..69bc518 100644 --- a/server.js +++ b/server.js @@ -55,45 +55,50 @@ const thoughtSchema = new mongoose.Schema({ const Thought = mongoose.model("Thought", thoughtSchema); -// Start defining routes here +// API Docs----- Routes ----- app.get("/", (req, res) => { - res.send("Welcome to the Happy Thoughts API ๐Ÿ’ฌ"); - endpoints: listEndpoints(app) + res.json({ + message: "Welcome to the Happy Thoughts API ๐Ÿ’ฌ", + endpoints: listEndpoints(app) + }); }); -app.get("/thoughts", (req, res) => { - let result = [...thoughts]; - const { heartsMin, category, sortBy, page, limit } = req.query; - - if (heartsMin) { - result = result.filter(t => t.hearts >= parseInt(heartsMin)); - } - - if (category) { - result = result.filter(t => t.category.toLowerCase() === category.toLowerCase()); - } - - if (sortBy === "hearts") { - result.sort((a, b) => b.hearts - a.hearts); - } else if (sortBy === "date") { - result.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)); +// Get all thoughts (with filtering, sorting, pagination) +app.get("/thoughts", async (req, res) => { + try { + const { heartsMin, category, sortBy, page, limit } = req.query; + let query = {}; + + if (heartsMin) { + query.hearts = { $gte: parseInt(heartsMin) }; + } + if (category) { + query.category = category; + } + + let sort = {}; + if (sortBy === "hearts") sort.hearts = -1; + if (sortBy === "date") sort.createdAt = -1; + + const pageInt = parseInt(page) || 1; + const limitInt = parseInt(limit) || 20; + const skip = (pageInt - 1) * limitInt; + + const thoughts = await Thought.find(query) + .sort(sort) + .skip(skip) + .limit(limitInt); + + res.json({ + page: pageInt, + results: thoughts + }); + } catch (err) { + res.status(500).json({ error: "Failed to fetch thoughts" }); } - - // Pagination - const pageInt = parseInt(page) || 1; - const limitInt = parseInt(limit) || result.length; - const start = (pageInt - 1) * limitInt; - const end = start + limitInt; - - const paginated = result.slice(start, end); - - res.json({ - page: pageInt, - total: result.length, - results: paginated - }); }); + app.get("/thoughts/:id", (req, res) => { const { id } = req.params; const thought = thoughts.find(t => t.id === parseInt(id)); From c6d786ed7d4bd9aa9e786383029229a9cbc45381 Mon Sep 17 00:00:00 2001 From: "DANUSHKA\\danus" Date: Tue, 12 Aug 2025 09:09:20 +0200 Subject: [PATCH 08/15] added other all routes --- server.js | 75 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 68 insertions(+), 7 deletions(-) diff --git a/server.js b/server.js index 69bc518..590b2bc 100644 --- a/server.js +++ b/server.js @@ -21,7 +21,7 @@ const app = express(); app.use(cors()); app.use(express.json()); -let thoughts = JSON.parse(fs.readFileSync('./data.json')); +//let thoughts = JSON.parse(fs.readFileSync('./data.json')); const mongoUrl = process.env.MONGO_URL || "mongodb://localhost:27017/happythoughts"; @@ -99,15 +99,76 @@ app.get("/thoughts", async (req, res) => { }); -app.get("/thoughts/:id", (req, res) => { - const { id } = req.params; - const thought = thoughts.find(t => t.id === parseInt(id)); +// Get single thought +app.get("/thoughts/:id", async (req, res) => { + try { + const thought = await Thought.findById(req.params.id); + if (!thought) { + return res.status(404).json({ error: "Thought not found" }); + } + res.json(thought); + } catch { + res.status(400).json({ error: "Invalid ID" }); + } +}); - if (!thought) { - return res.status(404).json({ error: "Thought not found." }); +// Create a new thought +app.post("/thoughts", async (req, res) => { + try { + const { message, category } = req.body; + const newThought = new Thought({ message, category }); + const savedThought = await newThought.save(); + res.status(201).json(savedThought); + } catch (err) { + res.status(400).json({ error: err.message }); } +}); - res.json(thought); +// Update a thought +app.put("/thoughts/:id", async (req, res) => { + try { + const updatedThought = await Thought.findByIdAndUpdate( + req.params.id, + req.body, + { new: true, runValidators: true } + ); + if (!updatedThought) { + return res.status(404).json({ error: "Thought not found" }); + } + res.json(updatedThought); + } catch (err) { + res.status(400).json({ error: err.message }); + } +}); + +// Delete a thought +app.delete("/thoughts/:id", async (req, res) => { + try { + const deletedThought = await Thought.findByIdAndDelete(req.params.id); + if (!deletedThought) { + return res.status(404).json({ error: "Thought not found" }); + } + res.json({ success: true }); + } catch (err) { + res.status(400).json({ error: err.message }); + } +}); + +// Like a thought +app.post("/thoughts/:id/like", async (req, res) => { + try { + const thought = await Thought.findByIdAndUpdate( + req.params.id, + { $inc: { hearts: 1 } }, + { new: true } + ); + if (!thought) { + return res.status(404).json({ error: "Thought not found" }); + } + res.json(thought); + } catch (err) { + res.status(400).json({ error: err.message }); + } }); // Start the server From c200fd2e5abaf43ac7c6475d13c971698a28684e Mon Sep 17 00:00:00 2001 From: "DANUSHKA\\danus" Date: Wed, 13 Aug 2025 00:09:50 +0200 Subject: [PATCH 09/15] remove dotenv file --- package.json | 1 + server.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index da9e6ff..96cd2f3 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": "^17.2.1", "e": "^0.2.33", "express": "^4.21.2", "express-list-endpoints": "^7.1.1", diff --git a/server.js b/server.js index 590b2bc..61afa51 100644 --- a/server.js +++ b/server.js @@ -3,13 +3,13 @@ import express from "express"; import listEndpoints from "express-list-endpoints"; import fs from "fs"; import mongoose from 'mongoose'; -import dotenv from "dotenv"; +//import dotenv from "dotenv"; // Load environment variables from .env file // This allows you to set environment variables like PORT and MONGO_URL // in a .env file for local development -dotenv.config(); +//dotenv.config(); // 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: From a59cfbab56055e7eba608aa186f34bd142d07f8a Mon Sep 17 00:00:00 2001 From: "DANUSHKA\\danus" Date: Wed, 13 Aug 2025 00:10:18 +0200 Subject: [PATCH 10/15] remove dotenv file --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index 61afa51..ae10c1f 100644 --- a/server.js +++ b/server.js @@ -14,7 +14,7 @@ import mongoose from 'mongoose'; // 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 || 80; +const port = process.env.PORT || 8080; const app = express(); // Add middlewares to enable cors and json body parsing From 217eaeb80db13b732fb4b43121fae993fcea144e Mon Sep 17 00:00:00 2001 From: "DANUSHKA\\danus" Date: Wed, 13 Aug 2025 12:47:32 +0200 Subject: [PATCH 11/15] added dotenv file --- server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index ae10c1f..9c7e45b 100644 --- a/server.js +++ b/server.js @@ -3,13 +3,13 @@ import express from "express"; import listEndpoints from "express-list-endpoints"; import fs from "fs"; import mongoose from 'mongoose'; -//import dotenv from "dotenv"; +import dotenv from "dotenv"; // Load environment variables from .env file // This allows you to set environment variables like PORT and MONGO_URL // in a .env file for local development -//dotenv.config(); +dotenv.config(); // 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: From 7fc2220f69861dce6770b5fb2c185aa9d942cdec Mon Sep 17 00:00:00 2001 From: "DANUSHKA\\danus" Date: Thu, 14 Aug 2025 20:14:58 +0200 Subject: [PATCH 12/15] addeded other routes and authentications --- package.json | 2 + server.js | 147 +++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 127 insertions(+), 22 deletions(-) diff --git a/package.json b/package.json index 96cd2f3..fc8a97c 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ "@babel/core": "^7.17.9", "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", + "bcrypt": "^6.0.0", "cors": "^2.8.5", + "crypto": "^1.0.1", "dotenv": "^17.2.1", "e": "^0.2.33", "express": "^4.21.2", diff --git a/server.js b/server.js index 9c7e45b..1be4e6c 100644 --- a/server.js +++ b/server.js @@ -1,13 +1,15 @@ import cors from "cors"; import express from "express"; import listEndpoints from "express-list-endpoints"; -import fs from "fs"; +import fs, { access } from "fs"; import mongoose from 'mongoose'; import dotenv from "dotenv"; +import bcrypt from "bcrypt"; +import crypto from "crypto"; // Load environment variables from .env file -// This allows you to set environment variables like PORT and MONGO_URL +// This allows to set environment variables like PORT and MONGO_URL // in a .env file for local development dotenv.config(); @@ -53,6 +55,91 @@ const thoughtSchema = new mongoose.Schema({ }); const Thought = mongoose.model("Thought", thoughtSchema); +//User model +const userSchema = new mongoose.Schema({ + username: { + type: String, + required: [true, "Username is required"], + unique: true, + minlength: [3, "Username must be at least 3 characters"], + maxlength: [20, "Username must be max 20 characters"] + }, + password: { + type: String, + required: [true, "Password is required"], + minlength: [6, "Password must be at least 6 characters"] + }, + accessToken: { + type: String, + default: () => crypto.randomBytes(16).toString("hex") + } +}); + +const User = mongoose.model("User", userSchema); + +// ----- Middleware for authentication ----- +const authenticateUser = async (req, res, next) => { + const accessToken = req.header("Authorization"); + try { + const user = await User.findOne({ accessToken }); + if (user) { + req.user = user; + next(); + } else { + res.status(401).json({ message: "Please log in" }); + } + } catch (err) { + res.status(403).json({ message: "Access denied" }); + } +}; + +// ----- User Registration ----- +app.post("/register", async (req, res) => { + try { + const { username, password } = req.body; + + if (password.length < 5) { + return res.status(400).json({ message: "Password must be at least 5 characters long" }); + } + + const salt = bcrypt.genSaltSync(); + const hashedPassword = bcrypt.hashSync(password, salt); + + const newUser = await new User({ + username, + password: hashedPassword + }).save(); + + res.status(201).json({ + username: newUser.username, + accessToken: newUser.accessToken + }); + } catch (err) { + if (err.code === 11000) { + res.status(400).json({ message: "That username already exists" }); + } else { + res.status(500).json({ message: "Internal server error", error: err }); + } + } +}); +// ----- User Login ----- +app.post("/login", async (req, res) => { + const { username, password } = req.body; + const user = await User.findOne({ username }); + + if (!user) { + return res.status(400).json({ message: "Username or password is incorrect" }); + } + + if (bcrypt.compareSync(password, user.password)) { + res.json({ + username: user.username, + accessToken: user.accessToken + }); + } else { + res.status(400).json({ message: "Username or password is incorrect" }); + } +}); // API Docs----- Routes ----- @@ -112,45 +199,61 @@ app.get("/thoughts/:id", async (req, res) => { } }); -// Create a new thought -app.post("/thoughts", async (req, res) => { +// Create new thought (authenticated) +app.post("/thoughts", authenticateUser, async (req, res) => { try { const { message, category } = req.body; - const newThought = new Thought({ message, category }); + const newThought = new Thought({ + message, + category, + createdBy: req.user.username + }); + const savedThought = await newThought.save(); res.status(201).json(savedThought); } catch (err) { - res.status(400).json({ error: err.message }); + res.status(400).json({ message: "Could not save thought", error: err }); } }); // Update a thought -app.put("/thoughts/:id", async (req, res) => { +app.patch("/thoughts/:id", authenticateUser, async (req, res) => { try { - const updatedThought = await Thought.findByIdAndUpdate( - req.params.id, - req.body, - { new: true, runValidators: true } - ); - if (!updatedThought) { - return res.status(404).json({ error: "Thought not found" }); + const thought = await Thought.findById(req.params.id); + + if (!thought) { + return res.status(404).json({ message: "Thought not found" }); } - res.json(updatedThought); + + if (thought.createdBy !== req.user.username) { + return res.status(403).json({ message: "Not allowed to edit this thought" }); + } + + thought.message = req.body.message || thought.message; + await thought.save(); + res.json(thought); } catch (err) { - res.status(400).json({ error: err.message }); + res.status(400).json({ message: "Could not update thought", error: err }); } }); // Delete a thought -app.delete("/thoughts/:id", async (req, res) => { +app.delete("/thoughts/:id", authenticateUser, async (req, res) => { try { - const deletedThought = await Thought.findByIdAndDelete(req.params.id); - if (!deletedThought) { - return res.status(404).json({ error: "Thought not found" }); + const thought = await Thought.findById(req.params.id); + + if (!thought) { + return res.status(404).json({ message: "Thought not found" }); + } + + if (thought.createdBy !== req.user.username) { + return res.status(403).json({ message: "Not allowed to delete this thought" }); } - res.json({ success: true }); + + await thought.deleteOne(); + res.json({ message: "Thought deleted successfully" }); } catch (err) { - res.status(400).json({ error: err.message }); + res.status(400).json({ message: "Could not delete thought", error: err }); } }); From 3b8c2f4b02698beee5d4136f0b0eb42fa4280e4f Mon Sep 17 00:00:00 2001 From: "DANUSHKA\\danus" Date: Thu, 4 Sep 2025 12:26:05 +0200 Subject: [PATCH 13/15] token authentication fix --- server.js | 74 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 41 insertions(+), 33 deletions(-) diff --git a/server.js b/server.js index 1be4e6c..eb94171 100644 --- a/server.js +++ b/server.js @@ -2,12 +2,11 @@ import cors from "cors"; import express from "express"; import listEndpoints from "express-list-endpoints"; import fs, { access } from "fs"; -import mongoose from 'mongoose'; +import mongoose from "mongoose"; import dotenv from "dotenv"; import bcrypt from "bcrypt"; import crypto from "crypto"; - // Load environment variables from .env file // This allows to set environment variables like PORT and MONGO_URL // in a .env file for local development @@ -23,10 +22,10 @@ const app = express(); app.use(cors()); app.use(express.json()); -//let thoughts = JSON.parse(fs.readFileSync('./data.json')); - +//let thoughts = JSON.parse(fs.readFileSync('./data.json')); -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost:27017/happythoughts"; +const mongoUrl = + process.env.MONGO_URL || "mongodb://localhost:27017/happythoughts"; // Connect to MongoDB mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true }); @@ -38,48 +37,54 @@ const thoughtSchema = new mongoose.Schema({ type: String, required: [true, "Message is required"], minlength: [5, "Message must be at least 5 characters"], - maxlength: [140, "Message must be max 140 characters"] + maxlength: [140, "Message must be max 140 characters"], }, hearts: { type: Number, - default: 0 + default: 0, }, category: { type: String, - default: "General" + default: "General", }, + createdBy: { + type: String, + required: true }, createdAt: { type: Date, - default: () => new Date() - } + default: () => new Date(), + }, }); const Thought = mongoose.model("Thought", thoughtSchema); //User model const userSchema = new mongoose.Schema({ username: { - type: String, + type: String, required: [true, "Username is required"], unique: true, minlength: [3, "Username must be at least 3 characters"], - maxlength: [20, "Username must be max 20 characters"] + maxlength: [20, "Username must be max 20 characters"], }, password: { type: String, - required: [true, "Password is required"], - minlength: [6, "Password must be at least 6 characters"] + required: [true, "Password is required"], + minlength: [6, "Password must be at least 6 characters"], }, accessToken: { type: String, - default: () => crypto.randomBytes(16).toString("hex") - } + default: () => crypto.randomBytes(16).toString("hex"), + }, }); const User = mongoose.model("User", userSchema); -// ----- Middleware for authentication ----- +// ----- Middleware for authentication ----- const authenticateUser = async (req, res, next) => { - const accessToken = req.header("Authorization"); + const raw = req.header("Authorization") || ""; + // accept "Bearer " OR raw "" + const accessToken = raw.startsWith("Bearer ") ? raw.slice(7) : raw; + try { const user = await User.findOne({ accessToken }); if (user) { @@ -99,7 +104,9 @@ app.post("/register", async (req, res) => { const { username, password } = req.body; if (password.length < 5) { - return res.status(400).json({ message: "Password must be at least 5 characters long" }); + return res + .status(400) + .json({ message: "Password must be at least 5 characters long" }); } const salt = bcrypt.genSaltSync(); @@ -107,12 +114,12 @@ app.post("/register", async (req, res) => { const newUser = await new User({ username, - password: hashedPassword + password: hashedPassword, }).save(); res.status(201).json({ username: newUser.username, - accessToken: newUser.accessToken + accessToken: newUser.accessToken, }); } catch (err) { if (err.code === 11000) { @@ -128,25 +135,26 @@ app.post("/login", async (req, res) => { const user = await User.findOne({ username }); if (!user) { - return res.status(400).json({ message: "Username or password is incorrect" }); + return res + .status(400) + .json({ message: "Username or password is incorrect" }); } if (bcrypt.compareSync(password, user.password)) { res.json({ username: user.username, - accessToken: user.accessToken + accessToken: user.accessToken, }); } else { res.status(400).json({ message: "Username or password is incorrect" }); } }); - // API Docs----- Routes ----- app.get("/", (req, res) => { res.json({ message: "Welcome to the Happy Thoughts API ๐Ÿ’ฌ", - endpoints: listEndpoints(app) + endpoints: listEndpoints(app), }); }); @@ -178,14 +186,13 @@ app.get("/thoughts", async (req, res) => { res.json({ page: pageInt, - results: thoughts + results: thoughts, }); } catch (err) { res.status(500).json({ error: "Failed to fetch thoughts" }); } }); - // Get single thought app.get("/thoughts/:id", async (req, res) => { try { @@ -206,7 +213,7 @@ app.post("/thoughts", authenticateUser, async (req, res) => { const newThought = new Thought({ message, category, - createdBy: req.user.username + createdBy: req.user.username, }); const savedThought = await newThought.save(); @@ -226,7 +233,9 @@ app.patch("/thoughts/:id", authenticateUser, async (req, res) => { } if (thought.createdBy !== req.user.username) { - return res.status(403).json({ message: "Not allowed to edit this thought" }); + return res + .status(403) + .json({ message: "Not allowed to edit this thought" }); } thought.message = req.body.message || thought.message; @@ -247,7 +256,9 @@ app.delete("/thoughts/:id", authenticateUser, async (req, res) => { } if (thought.createdBy !== req.user.username) { - return res.status(403).json({ message: "Not allowed to delete this thought" }); + return res + .status(403) + .json({ message: "Not allowed to delete this thought" }); } await thought.deleteOne(); @@ -278,6 +289,3 @@ app.post("/thoughts/:id/like", async (req, res) => { app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); - - - From 24532556cd3d5202fd6d407a118edea9da004ed5 Mon Sep 17 00:00:00 2001 From: "DANUSHKA\\danus" Date: Thu, 4 Sep 2025 15:05:18 +0200 Subject: [PATCH 14/15] cors error fixed --- server.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index eb94171..240417b 100644 --- a/server.js +++ b/server.js @@ -19,7 +19,12 @@ const port = process.env.PORT || 8080; const app = express(); // Add middlewares to enable cors and json body parsing -app.use(cors()); +app.use(cors({ + origin: ["https://happythoughtsappbydanu.netlify.app/"], // add your deployed FE origin here later + methods: ["GET", "POST", "PATCH", "DELETE", "OPTIONS"], + allowedHeaders: ["Content-Type", "Authorization"], + // credentials: true, // only if you ever use cookies +})); app.use(express.json()); //let thoughts = JSON.parse(fs.readFileSync('./data.json')); From aa0ec2266e1407b0b636a3f95f85c52992631979 Mon Sep 17 00:00:00 2001 From: "DANUSHKA\\danus" Date: Thu, 4 Sep 2025 15:33:37 +0200 Subject: [PATCH 15/15] cors error fixed --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index 240417b..7f58648 100644 --- a/server.js +++ b/server.js @@ -20,7 +20,7 @@ const app = express(); // Add middlewares to enable cors and json body parsing app.use(cors({ - origin: ["https://happythoughtsappbydanu.netlify.app/"], // add your deployed FE origin here later + origin: ["http://localhost:5174", "https://happythoughtsappbydanu.netlify.app"], // add your deployed FE origin here later methods: ["GET", "POST", "PATCH", "DELETE", "OPTIONS"], allowedHeaders: ["Content-Type", "Authorization"], // credentials: true, // only if you ever use cookies