From 68bbe0cec1159d394faa02e2d5c2c195a25f628f Mon Sep 17 00:00:00 2001 From: Oscar Liljefors Date: Mon, 21 Jul 2025 21:59:47 +0200 Subject: [PATCH 01/15] start --- server.js | 40 ++++++++++++++++++++++++++++++---------- thoughts.json | 23 +++++++++++++++++++++++ 2 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 thoughts.json diff --git a/server.js b/server.js index f47771b..778f9b8 100644 --- a/server.js +++ b/server.js @@ -1,22 +1,42 @@ -import cors from "cors" -import express from "express" +import express from "express"; +import listEndpoints from "express-list-endpoints"; +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 app.get("/", (req, res) => { - res.send("Hello Technigo!") -}) + res.json({ + message: "Welcome to Oscar's Thoughts Api!", + endpoints: listEndpoints(app), + }); +}); + +app.get("/thoughts", (req, res) => { + res.json(thoughts); +}); + +app.get("/thoughts/:id", (req, res) => { + const { id } = req.params; + const thought = thoughts.find((t) => t.id === +id); + + if (thought) { + res.json(thought); + } else { + res.status(404).json({ error: "Thought not found" }); + } +}); // Start the server app.listen(port, () => { - console.log(`Server running on http://localhost:${port}`) -}) + console.log(`Server running on http://localhost:${port}`); +}); diff --git a/thoughts.json b/thoughts.json new file mode 100644 index 0000000..af5c6b8 --- /dev/null +++ b/thoughts.json @@ -0,0 +1,23 @@ +[ + { + "id": 1, + "text": "I love coding!", + "category": "Project thoughts", + "hearts": 12, + "createdAt": "2025-07-20T12:00:00Z" + }, + { + "id": 2, + "text": "Tacos for lunch today?", + "category": "Food thoughts", + "hearts": 5, + "createdAt": "2025-07-19T15:30:00Z" + }, + { + "id": 3, + "text": "Remember to water the plants.", + "category": "Home thoughts", + "hearts": 3, + "createdAt": "2025-07-18T08:15:00Z" + } +] From 1c357e0d92c86fd074235e2e4bad903a3a567ec6 Mon Sep 17 00:00:00 2001 From: Oscar Liljefors Date: Mon, 21 Jul 2025 22:07:08 +0200 Subject: [PATCH 02/15] core api --- package.json | 1 + server.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index bf25bb6..00addae 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@babel/preset-env": "^7.16.11", "cors": "^2.8.5", "express": "^4.17.3", + "express-list-endpoints": "^7.1.1", "nodemon": "^3.0.1" } } diff --git a/server.js b/server.js index 778f9b8..1966e6b 100644 --- a/server.js +++ b/server.js @@ -1,7 +1,7 @@ import express from "express"; import listEndpoints from "express-list-endpoints"; import cors from "cors"; -import express from "express"; +import thoughts from "./thoughts.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: From b20ea7d502842d8fd61c42a2064441309e68935a Mon Sep 17 00:00:00 2001 From: Oscar Liljefors Date: Mon, 21 Jul 2025 22:14:26 +0200 Subject: [PATCH 03/15] working api --- server.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/server.js b/server.js index 1966e6b..b98caa9 100644 --- a/server.js +++ b/server.js @@ -21,9 +21,35 @@ app.get("/", (req, res) => { }); }); +app.get("/thoughts", (req, res) => { + const { page = 1, limit = 5 } = req.query; + + // Convert query strings to numbers + const pageNum = parseInt(page); + const limitNum = parseInt(limit); + + // Calculate starting and ending index + const startIndex = (pageNum - 1) * limitNum; + const endIndex = startIndex + limitNum; + + // Slice the array + const paginatedThoughts = thoughts.slice(startIndex, endIndex); + + // Response + res.json({ + page: pageNum, + limit: limitNum, + totalThoughts: thoughts.length, + totalPages: Math.ceil(thoughts.length / limitNum), + results: paginatedThoughts, + }); +}); +/* +get all thoughts app.get("/thoughts", (req, res) => { res.json(thoughts); }); +*/ app.get("/thoughts/:id", (req, res) => { const { id } = req.params; From fd3738913ccc1751d03927db1837f69957af12b7 Mon Sep 17 00:00:00 2001 From: Oscar Liljefors Date: Mon, 21 Jul 2025 22:54:44 +0200 Subject: [PATCH 04/15] mongoose started code --- package.json | 1 + server.js | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/package.json b/package.json index 00addae..fefbebd 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "cors": "^2.8.5", "express": "^4.17.3", "express-list-endpoints": "^7.1.1", + "mongoose": "^8.16.4", "nodemon": "^3.0.1" } } diff --git a/server.js b/server.js index b98caa9..ab3a0ea 100644 --- a/server.js +++ b/server.js @@ -2,6 +2,7 @@ import express from "express"; import listEndpoints from "express-list-endpoints"; import cors from "cors"; import thoughts from "./thoughts.json"; +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: @@ -13,6 +14,8 @@ const app = express(); app.use(cors()); app.use(express.json()); +const mongoURL = process.env.mongoURL || "mongodb://localhost"; + // Start defining your routes here app.get("/", (req, res) => { res.json({ @@ -21,6 +24,9 @@ app.get("/", (req, res) => { }); }); +/*get paginated thoughts, e.g +http://localhost:8080/thoughts?page=1&limit=2 +returns 2 thoughts */ app.get("/thoughts", (req, res) => { const { page = 1, limit = 5 } = req.query; @@ -51,6 +57,7 @@ app.get("/thoughts", (req, res) => { }); */ +/*get thought by id */ app.get("/thoughts/:id", (req, res) => { const { id } = req.params; const thought = thoughts.find((t) => t.id === +id); From 45ca1ede5e4cf5d44727354a1e6b2be540dec025 Mon Sep 17 00:00:00 2001 From: Oscar Liljefors Date: Tue, 22 Jul 2025 02:40:44 +0200 Subject: [PATCH 05/15] added get, post, patch, and delete routes --- package.json | 1 + server.js | 130 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 126 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index fefbebd..1c9f53d 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.0", "express": "^4.17.3", "express-list-endpoints": "^7.1.1", "mongoose": "^8.16.4", diff --git a/server.js b/server.js index ab3a0ea..a20005a 100644 --- a/server.js +++ b/server.js @@ -14,19 +14,66 @@ const app = express(); app.use(cors()); app.use(express.json()); -const mongoURL = process.env.mongoURL || "mongodb://localhost"; +/*mongoose starting code */ +const mongoURL = process.env.mongoURL || "mongodb://localhost/collection"; +mongoose.connect(mongoURL, { useNewUrlParser: true, useUnifiedTopology: true }); +mongoose.Promise = Promise; + +const ThoughtSchema = new mongoose.Schema({ + message: { + type: String, + required: [true, "Message is required"], + minlength: [5, "Message too short"], + maxlength: [140, "Message too long"], + }, + hearts: { + type: Number, + default: 0, + }, + createdAt: { + type: Date, + default: Date.now, + }, +}); + +const Thought = mongoose.model("Thought", ThoughtSchema); // Start defining your routes here app.get("/", (req, res) => { res.json({ - message: "Welcome to Oscar's Thoughts Api!", + message: "Welcome to Oscar's Thoughts API!", endpoints: listEndpoints(app), }); }); -/*get paginated thoughts, e.g +app.get("/thoughts", async (req, res) => { + const { page = 1, limit = 5 } = req.query; + + try { + const totalThoughts = await Thought.countDocuments(); + const thoughts = await Thought.find() + .sort({ createdAt: -1 }) + .skip((page - 1) * limit) + .limit(limit); + + res.json({ + page: Number(page), + limit: Number(limit), + totalThoughts, + totalPages: Math.ceil(totalThoughts / limit), + results: thoughts, + }); + } catch (err) { + res.status(500).json({ error: "Could not fetch thoughts" }); + } +}); + +/* + +get paginated thoughts, e.g http://localhost:8080/thoughts?page=1&limit=2 -returns 2 thoughts */ +returns 2 thoughts + app.get("/thoughts", (req, res) => { const { page = 1, limit = 5 } = req.query; @@ -50,14 +97,34 @@ app.get("/thoughts", (req, res) => { results: paginatedThoughts, }); }); + +*/ + /* get all thoughts + app.get("/thoughts", (req, res) => { res.json(thoughts); }); */ -/*get thought by id */ +app.get("/thoughts/:id", async (req, res) => { + const { id } = req.params; + + try { + const thought = await Thought.findById(id); + if (!thought) { + return res.status(404).json({ error: "Thought not found" }); + } + res.json(thought); + } catch (err) { + res.status(400).json({ error: "Invalid ID" }); + } +}); + +/* +get thought by id + app.get("/thoughts/:id", (req, res) => { const { id } = req.params; const thought = thoughts.find((t) => t.id === +id); @@ -68,6 +135,59 @@ app.get("/thoughts/:id", (req, res) => { res.status(404).json({ error: "Thought not found" }); } }); +*/ + +/*add a thought */ +app.post("/thoughts", async (req, res) => { + const { message } = req.body; + + try { + const newThought = new Thought({ message }); + const savedThought = await newThought.save(); + res.status(201).json(savedThought); + } catch (err) { + res.status(400).json({ error: err.message }); + } +}); + +/*chnage a thought */ +app.patch("/thoughts/:id", async (req, res) => { + const { id } = req.params; + const { message } = req.body; + + try { + const updated = await Thought.findByIdAndUpdate( + id, + { message }, + { new: true, runValidators: true } + ); + + if (!updated) { + return res.status(404).json({ error: "Thought not found" }); + } + + res.json(updated); + } catch (err) { + res.status(400).json({ error: err.message }); + } +}); + +/*remove a thought */ +app.delete("/thoughts/:id", async (req, res) => { + const { id } = req.params; + + try { + const deleted = await Thought.findByIdAndDelete(id); + + if (!deleted) { + return res.status(404).json({ error: "Thought not found" }); + } + + res.status(204).end(); + } catch (err) { + res.status(400).json({ error: "Invalid ID" }); + } +}); // Start the server app.listen(port, () => { From b143e8006975bba8a95bde5e0fccc233e0084b40 Mon Sep 17 00:00:00 2001 From: Oscar Liljefors Date: Tue, 22 Jul 2025 02:52:59 +0200 Subject: [PATCH 06/15] fix --- server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index a20005a..ecb53a7 100644 --- a/server.js +++ b/server.js @@ -15,8 +15,8 @@ app.use(cors()); app.use(express.json()); /*mongoose starting code */ -const mongoURL = process.env.mongoURL || "mongodb://localhost/collection"; -mongoose.connect(mongoURL, { useNewUrlParser: true, useUnifiedTopology: true }); +const mongoURL = process.env.mongoURL; +mongoose.connect(mongoURL); mongoose.Promise = Promise; const ThoughtSchema = new mongoose.Schema({ From 25ac40bc14a5e87771c7e5260f5d379c1dd15d2c Mon Sep 17 00:00:00 2001 From: Oscar Liljefors Date: Tue, 22 Jul 2025 03:26:19 +0200 Subject: [PATCH 07/15] being able to like a thought? --- server.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/server.js b/server.js index ecb53a7..38499df 100644 --- a/server.js +++ b/server.js @@ -150,6 +150,24 @@ app.post("/thoughts", async (req, res) => { } }); +/*like an id, off a thought */ +app.post("/thoughts/:id/like", async (req, res) => { + const { id } = req.params; + try { + const updatedThought = await Thought.findByIdAndUpdate( + id, + { $inc: { hearts: 1 } }, + { new: true } + ); + if (!updatedThought) { + return res.status(404).json({ error: "Thought not found" }); + } + res.status(200).json(updatedThought); + } catch (err) { + res.status(400).json({ error: err.message }); + } +}); + /*chnage a thought */ app.patch("/thoughts/:id", async (req, res) => { const { id } = req.params; From 2f1a16f6f0970ad4f8e057bf32f9436622fdd821 Mon Sep 17 00:00:00 2001 From: Oscar Liljefors Date: Thu, 24 Jul 2025 01:10:39 +0200 Subject: [PATCH 08/15] fixes and login options --- package.json | 1 + server.js | 1 - thoughts.json => thoughts_IGNORE.json | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename thoughts.json => thoughts_IGNORE.json (100%) diff --git a/package.json b/package.json index 1c9f53d..e465568 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@babel/core": "^7.17.9", "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", + "bcrypt": "^6.0.0", "cors": "^2.8.5", "dotenv": "^17.2.0", "express": "^4.17.3", diff --git a/server.js b/server.js index 38499df..f1f589a 100644 --- a/server.js +++ b/server.js @@ -1,7 +1,6 @@ import express from "express"; import listEndpoints from "express-list-endpoints"; import cors from "cors"; -import thoughts from "./thoughts.json"; import mongoose from "mongoose"; // Defines the port the app will run on. Defaults to 8080, but can be overridden diff --git a/thoughts.json b/thoughts_IGNORE.json similarity index 100% rename from thoughts.json rename to thoughts_IGNORE.json From 3614bbea1d1d04ad6147f73b6431094e41c90e23 Mon Sep 17 00:00:00 2001 From: Oscar Liljefors Date: Thu, 24 Jul 2025 01:18:02 +0200 Subject: [PATCH 09/15] Finalizing --- server.js | 130 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 113 insertions(+), 17 deletions(-) diff --git a/server.js b/server.js index f1f589a..cb6209f 100644 --- a/server.js +++ b/server.js @@ -2,6 +2,8 @@ import express from "express"; import listEndpoints from "express-list-endpoints"; import cors from "cors"; import mongoose from "mongoose"; +import crypto from "crypto"; +import bcrypt from "bcrypt"; // 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: @@ -33,10 +35,34 @@ const ThoughtSchema = new mongoose.Schema({ type: Date, default: Date.now, }, + createdBy: { + type: mongoose.Schema.Types.ObjectId, + ref: "User", + }, }); const Thought = mongoose.model("Thought", ThoughtSchema); +const UserSchema = new mongoose.Schema({ + username: { + type: String, + required: [true, "Username is required"], + unique: true, + minlength: 3, + }, + password: { + type: String, + required: [true, "Password is required"], + minlength: 6, + }, + accessToken: { + type: String, + default: () => crypto.randomBytes(128).toString("hex"), + }, +}); + +const User = mongoose.model("User", UserSchema); + // Start defining your routes here app.get("/", (req, res) => { res.json({ @@ -137,11 +163,11 @@ app.get("/thoughts/:id", (req, res) => { */ /*add a thought */ -app.post("/thoughts", async (req, res) => { +app.post("/thoughts", authenticateUser, async (req, res) => { const { message } = req.body; try { - const newThought = new Thought({ message }); + const newThought = new Thought({ message, createdBy: req.user._id }); const savedThought = await newThought.save(); res.status(201).json(savedThought); } catch (err) { @@ -168,44 +194,114 @@ app.post("/thoughts/:id/like", async (req, res) => { }); /*chnage a thought */ -app.patch("/thoughts/:id", async (req, res) => { +app.patch("/thoughts/:id", authenticateUser, async (req, res) => { const { id } = req.params; const { message } = req.body; try { - const updated = await Thought.findByIdAndUpdate( - id, - { message }, - { new: true, runValidators: true } - ); - - if (!updated) { - return res.status(404).json({ error: "Thought not found" }); + const thought = await Thought.findById(id); + if (!thought) return res.status(404).json({ error: "Thought not found" }); + if (String(thought.createdBy) !== String(req.user._id)) { + return res + .status(403) + .json({ error: "You are not allowed to edit this thought" }); } - res.json(updated); + thought.message = message; + await thought.save(); + res.json(thought); } catch (err) { res.status(400).json({ error: err.message }); } }); /*remove a thought */ -app.delete("/thoughts/:id", async (req, res) => { +app.delete("/thoughts/:id", authenticateUser, async (req, res) => { const { id } = req.params; try { - const deleted = await Thought.findByIdAndDelete(id); - - if (!deleted) { - return res.status(404).json({ error: "Thought not found" }); + const thought = await Thought.findById(id); + if (!thought) return res.status(404).json({ error: "Thought not found" }); + if (String(thought.createdBy) !== String(req.user._id)) { + return res + .status(403) + .json({ error: "You are not allowed to delete this thought" }); } + await thought.deleteOne(); res.status(204).end(); } catch (err) { res.status(400).json({ error: "Invalid ID" }); } }); +app.post("/register", async (req, res) => { + const { username, password } = req.body; + + try { + if (!username || !password) { + return res + .status(400) + .json({ error: "Username and password are required" }); + } + + const existingUser = await User.findOne({ username }); + if (existingUser) { + return res.status(400).json({ error: "Username already exists" }); + } + + const salt = bcrypt.genSaltSync(); + const hashedPassword = bcrypt.hashSync(password, salt); + + const newUser = new User({ username, password: hashedPassword }); + await newUser.save(); + + res.status(201).json({ + username: newUser.username, + id: newUser._id, + accessToken: newUser.accessToken, + }); + } catch (err) { + res.status(400).json({ error: err.message }); + } +}); + +app.post("/login", async (req, res) => { + const { username, password } = req.body; + + try { + const user = await User.findOne({ username }); + + if (!user || !bcrypt.compareSync(password, user.password)) { + return res.status(401).json({ error: "Invalid username or password" }); + } + + res.status(200).json({ + username: user.username, + id: user._id, + accessToken: user.accessToken, + }); + } catch (err) { + res.status(400).json({ error: "Something went wrong" }); + } +}); + +/*auth middleware */ +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({ error: "Please log in to access this resource" }); + } + } catch (err) { + res.status(401).json({ error: "Invalid request" }); + } +}; // Start the server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); From 84a446b35934339ffcd9835ec2e9496da8266c1d Mon Sep 17 00:00:00 2001 From: Oscar Liljefors Date: Thu, 24 Jul 2025 02:00:45 +0200 Subject: [PATCH 10/15] redeploy --- server.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server.js b/server.js index cb6209f..3c10509 100644 --- a/server.js +++ b/server.js @@ -123,6 +123,8 @@ app.get("/thoughts", (req, res) => { }); }); + +r */ /* From 0c433cc6f10ff09f0526658db1a76256f79ef28f Mon Sep 17 00:00:00 2001 From: Oscar Liljefors Date: Thu, 24 Jul 2025 02:06:38 +0200 Subject: [PATCH 11/15] major fixes --- server.js | 139 ++++++++++++------------------------------------------ 1 file changed, 31 insertions(+), 108 deletions(-) diff --git a/server.js b/server.js index 3c10509..dccbf3b 100644 --- a/server.js +++ b/server.js @@ -5,17 +5,12 @@ import mongoose from "mongoose"; import crypto from "crypto"; import bcrypt from "bcrypt"; -// 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()); -/*mongoose starting code */ const mongoURL = process.env.mongoURL; mongoose.connect(mongoURL); mongoose.Promise = Promise; @@ -44,9 +39,9 @@ const ThoughtSchema = new mongoose.Schema({ const Thought = mongoose.model("Thought", ThoughtSchema); const UserSchema = new mongoose.Schema({ - username: { + email: { type: String, - required: [true, "Username is required"], + required: [true, "Email is required"], unique: true, minlength: 3, }, @@ -63,7 +58,21 @@ const UserSchema = new mongoose.Schema({ const User = mongoose.model("User", UserSchema); -// Start defining your routes here +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({ error: "Please log in to access this resource" }); + } + } catch (err) { + res.status(401).json({ error: "Invalid request" }); + } +}; + app.get("/", (req, res) => { res.json({ message: "Welcome to Oscar's Thoughts API!", @@ -73,7 +82,6 @@ app.get("/", (req, res) => { app.get("/thoughts", async (req, res) => { const { page = 1, limit = 5 } = req.query; - try { const totalThoughts = await Thought.countDocuments(); const thoughts = await Thought.find() @@ -93,51 +101,8 @@ app.get("/thoughts", async (req, res) => { } }); -/* - -get paginated thoughts, e.g -http://localhost:8080/thoughts?page=1&limit=2 -returns 2 thoughts - -app.get("/thoughts", (req, res) => { - const { page = 1, limit = 5 } = req.query; - - // Convert query strings to numbers - const pageNum = parseInt(page); - const limitNum = parseInt(limit); - - // Calculate starting and ending index - const startIndex = (pageNum - 1) * limitNum; - const endIndex = startIndex + limitNum; - - // Slice the array - const paginatedThoughts = thoughts.slice(startIndex, endIndex); - - // Response - res.json({ - page: pageNum, - limit: limitNum, - totalThoughts: thoughts.length, - totalPages: Math.ceil(thoughts.length / limitNum), - results: paginatedThoughts, - }); -}); - - -r -*/ - -/* -get all thoughts - -app.get("/thoughts", (req, res) => { - res.json(thoughts); -}); -*/ - app.get("/thoughts/:id", async (req, res) => { const { id } = req.params; - try { const thought = await Thought.findById(id); if (!thought) { @@ -149,25 +114,8 @@ app.get("/thoughts/:id", async (req, res) => { } }); -/* -get thought by id - -app.get("/thoughts/:id", (req, res) => { - const { id } = req.params; - const thought = thoughts.find((t) => t.id === +id); - - if (thought) { - res.json(thought); - } else { - res.status(404).json({ error: "Thought not found" }); - } -}); -*/ - -/*add a thought */ app.post("/thoughts", authenticateUser, async (req, res) => { const { message } = req.body; - try { const newThought = new Thought({ message, createdBy: req.user._id }); const savedThought = await newThought.save(); @@ -177,7 +125,6 @@ app.post("/thoughts", authenticateUser, async (req, res) => { } }); -/*like an id, off a thought */ app.post("/thoughts/:id/like", async (req, res) => { const { id } = req.params; try { @@ -195,7 +142,6 @@ app.post("/thoughts/:id/like", async (req, res) => { } }); -/*chnage a thought */ app.patch("/thoughts/:id", authenticateUser, async (req, res) => { const { id } = req.params; const { message } = req.body; @@ -204,9 +150,7 @@ app.patch("/thoughts/:id", authenticateUser, async (req, res) => { const thought = await Thought.findById(id); if (!thought) return res.status(404).json({ error: "Thought not found" }); if (String(thought.createdBy) !== String(req.user._id)) { - return res - .status(403) - .json({ error: "You are not allowed to edit this thought" }); + return res.status(403).json({ error: "Not your thought to edit" }); } thought.message = message; @@ -217,7 +161,6 @@ app.patch("/thoughts/:id", authenticateUser, async (req, res) => { } }); -/*remove a thought */ app.delete("/thoughts/:id", authenticateUser, async (req, res) => { const { id } = req.params; @@ -225,9 +168,7 @@ app.delete("/thoughts/:id", authenticateUser, async (req, res) => { const thought = await Thought.findById(id); if (!thought) return res.status(404).json({ error: "Thought not found" }); if (String(thought.createdBy) !== String(req.user._id)) { - return res - .status(403) - .json({ error: "You are not allowed to delete this thought" }); + return res.status(403).json({ error: "Not your thought to delete" }); } await thought.deleteOne(); @@ -238,28 +179,26 @@ app.delete("/thoughts/:id", authenticateUser, async (req, res) => { }); app.post("/register", async (req, res) => { - const { username, password } = req.body; + const { email, password } = req.body; try { - if (!username || !password) { - return res - .status(400) - .json({ error: "Username and password are required" }); + if (!email || !password) { + return res.status(400).json({ error: "Email and password are required" }); } - const existingUser = await User.findOne({ username }); + const existingUser = await User.findOne({ email }); if (existingUser) { - return res.status(400).json({ error: "Username already exists" }); + return res.status(400).json({ error: "Email already exists" }); } const salt = bcrypt.genSaltSync(); const hashedPassword = bcrypt.hashSync(password, salt); - const newUser = new User({ username, password: hashedPassword }); + const newUser = new User({ email, password: hashedPassword }); await newUser.save(); res.status(201).json({ - username: newUser.username, + email: newUser.email, id: newUser._id, accessToken: newUser.accessToken, }); @@ -269,17 +208,17 @@ app.post("/register", async (req, res) => { }); app.post("/login", async (req, res) => { - const { username, password } = req.body; + const { email, password } = req.body; try { - const user = await User.findOne({ username }); + const user = await User.findOne({ email }); if (!user || !bcrypt.compareSync(password, user.password)) { - return res.status(401).json({ error: "Invalid username or password" }); + return res.status(401).json({ error: "Invalid email or password" }); } res.status(200).json({ - username: user.username, + email: user.email, id: user._id, accessToken: user.accessToken, }); @@ -288,23 +227,7 @@ app.post("/login", async (req, res) => { } }); -/*auth middleware */ -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({ error: "Please log in to access this resource" }); - } - } catch (err) { - res.status(401).json({ error: "Invalid request" }); - } -}; -// Start the server +// 🚀 Start server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); From 9d56b8f33be3825f6974bc8d951350b7b1c9509d Mon Sep 17 00:00:00 2001 From: Oscar Liljefors Date: Thu, 24 Jul 2025 02:48:00 +0200 Subject: [PATCH 12/15] review --- server.js | 1 + 1 file changed, 1 insertion(+) diff --git a/server.js b/server.js index dccbf3b..b2cf246 100644 --- a/server.js +++ b/server.js @@ -36,6 +36,7 @@ const ThoughtSchema = new mongoose.Schema({ }, }); +/* */ const Thought = mongoose.model("Thought", ThoughtSchema); const UserSchema = new mongoose.Schema({ From 952f73844cd301c9cbff4989f145972d299d208d Mon Sep 17 00:00:00 2001 From: Oscar Liljefors Date: Thu, 24 Jul 2025 03:15:07 +0200 Subject: [PATCH 13/15] changes --- data.json | 12 ++++++------ server.js | 1 - 2 files changed, 6 insertions(+), 7 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 b2cf246..dccbf3b 100644 --- a/server.js +++ b/server.js @@ -36,7 +36,6 @@ const ThoughtSchema = new mongoose.Schema({ }, }); -/* */ const Thought = mongoose.model("Thought", ThoughtSchema); const UserSchema = new mongoose.Schema({ From 8b8d3d88fba7f5092ecba1ee425b1467c472b864 Mon Sep 17 00:00:00 2001 From: Oscar Liljefors Date: Thu, 24 Jul 2025 03:21:01 +0200 Subject: [PATCH 14/15] hat --- server.js | 163 +++++++++++++++++++++++++++--------------------------- 1 file changed, 81 insertions(+), 82 deletions(-) diff --git a/server.js b/server.js index dccbf3b..836e625 100644 --- a/server.js +++ b/server.js @@ -5,22 +5,41 @@ import mongoose from "mongoose"; import crypto from "crypto"; import bcrypt from "bcrypt"; +// Setup const port = process.env.PORT || 8080; const app = express(); +const mongoURL = process.env.mongoURL || "mongodb://127.0.0.1/happy-thoughts"; +mongoose.connect(mongoURL); +mongoose.Promise = Promise; app.use(cors()); app.use(express.json()); -const mongoURL = process.env.mongoURL; -mongoose.connect(mongoURL); -mongoose.Promise = Promise; +// Schemas +const UserSchema = new mongoose.Schema({ + email: { + type: String, + required: [true, "Email is required"], + unique: true, + match: [/.+@.+\..+/, "Invalid email format"], + }, + password: { + type: String, + required: true, + minlength: 6, + }, + accessToken: { + type: String, + default: () => crypto.randomBytes(128).toString("hex"), + }, +}); const ThoughtSchema = new mongoose.Schema({ message: { type: String, required: [true, "Message is required"], - minlength: [5, "Message too short"], - maxlength: [140, "Message too long"], + minlength: 5, + maxlength: 140, }, hearts: { type: Number, @@ -33,31 +52,14 @@ const ThoughtSchema = new mongoose.Schema({ createdBy: { type: mongoose.Schema.Types.ObjectId, ref: "User", - }, -}); - -const Thought = mongoose.model("Thought", ThoughtSchema); - -const UserSchema = new mongoose.Schema({ - email: { - type: String, - required: [true, "Email is required"], - unique: true, - minlength: 3, - }, - password: { - type: String, - required: [true, "Password is required"], - minlength: 6, - }, - accessToken: { - type: String, - default: () => crypto.randomBytes(128).toString("hex"), + default: null, }, }); const User = mongoose.model("User", UserSchema); +const Thought = mongoose.model("Thought", ThoughtSchema); +// Auth middleware const authenticateUser = async (req, res, next) => { const accessToken = req.header("Authorization"); try { @@ -68,11 +70,12 @@ const authenticateUser = async (req, res, next) => { } else { res.status(401).json({ error: "Please log in to access this resource" }); } - } catch (err) { + } catch { res.status(401).json({ error: "Invalid request" }); } }; +// Routes app.get("/", (req, res) => { res.json({ message: "Welcome to Oscar's Thoughts API!", @@ -87,73 +90,74 @@ app.get("/thoughts", async (req, res) => { const thoughts = await Thought.find() .sort({ createdAt: -1 }) .skip((page - 1) * limit) - .limit(limit); - + .limit(Number(limit)); res.json({ page: Number(page), - limit: Number(limit), totalThoughts, totalPages: Math.ceil(totalThoughts / limit), results: thoughts, }); - } catch (err) { + } catch { res.status(500).json({ error: "Could not fetch thoughts" }); } }); app.get("/thoughts/:id", async (req, res) => { - const { id } = req.params; try { - const thought = await Thought.findById(id); - if (!thought) { - return res.status(404).json({ error: "Thought not found" }); - } + const thought = await Thought.findById(req.params.id); + if (!thought) return res.status(404).json({ error: "Thought not found" }); res.json(thought); - } catch (err) { + } catch { res.status(400).json({ error: "Invalid ID" }); } }); -app.post("/thoughts", authenticateUser, async (req, res) => { +app.post("/thoughts", async (req, res) => { const { message } = req.body; + let createdBy = null; + + const token = req.header("Authorization"); + if (token) { + const user = await User.findOne({ accessToken: token }); + if (user) createdBy = user._id; + } + try { - const newThought = new Thought({ message, createdBy: req.user._id }); - const savedThought = await newThought.save(); - res.status(201).json(savedThought); + const thought = new Thought({ message, createdBy }); + const saved = await thought.save(); + res.status(201).json(saved); } catch (err) { res.status(400).json({ error: err.message }); } }); app.post("/thoughts/:id/like", async (req, res) => { - const { id } = req.params; try { - const updatedThought = await Thought.findByIdAndUpdate( - id, + const updated = await Thought.findByIdAndUpdate( + req.params.id, { $inc: { hearts: 1 } }, { new: true } ); - if (!updatedThought) { - return res.status(404).json({ error: "Thought not found" }); - } - res.status(200).json(updatedThought); - } catch (err) { - res.status(400).json({ error: err.message }); + if (!updated) return res.status(404).json({ error: "Thought not found" }); + res.status(200).json(updated); + } catch { + res.status(400).json({ error: "Invalid ID" }); } }); app.patch("/thoughts/:id", authenticateUser, async (req, res) => { - const { id } = req.params; - const { message } = req.body; - try { - const thought = await Thought.findById(id); + const thought = await Thought.findById(req.params.id); if (!thought) return res.status(404).json({ error: "Thought not found" }); - if (String(thought.createdBy) !== String(req.user._id)) { - return res.status(403).json({ error: "Not your thought to edit" }); + if ( + !thought.createdBy || + thought.createdBy.toString() !== req.user._id.toString() + ) { + return res + .status(403) + .json({ error: "Not allowed to edit this thought" }); } - - thought.message = message; + thought.message = req.body.message; await thought.save(); res.json(thought); } catch (err) { @@ -162,40 +166,38 @@ app.patch("/thoughts/:id", authenticateUser, async (req, res) => { }); app.delete("/thoughts/:id", authenticateUser, async (req, res) => { - const { id } = req.params; - try { - const thought = await Thought.findById(id); + const thought = await Thought.findById(req.params.id); if (!thought) return res.status(404).json({ error: "Thought not found" }); - if (String(thought.createdBy) !== String(req.user._id)) { - return res.status(403).json({ error: "Not your thought to delete" }); + if ( + !thought.createdBy || + thought.createdBy.toString() !== req.user._id.toString() + ) { + return res + .status(403) + .json({ error: "Not allowed to delete this thought" }); } - await thought.deleteOne(); res.status(204).end(); - } catch (err) { + } catch { res.status(400).json({ error: "Invalid ID" }); } }); app.post("/register", async (req, res) => { const { email, password } = req.body; - try { - if (!email || !password) { - return res.status(400).json({ error: "Email and password are required" }); - } - - const existingUser = await User.findOne({ email }); - if (existingUser) { - return res.status(400).json({ error: "Email already exists" }); - } + if (!email || !password) + return res.status(400).json({ error: "All fields are required" }); - const salt = bcrypt.genSaltSync(); - const hashedPassword = bcrypt.hashSync(password, salt); + const existing = await User.findOne({ email }); + if (existing) + return res + .status(400) + .json({ error: "That email address already exists" }); - const newUser = new User({ email, password: hashedPassword }); - await newUser.save(); + const hashed = bcrypt.hashSync(password, bcrypt.genSaltSync()); + const newUser = await new User({ email, password: hashed }).save(); res.status(201).json({ email: newUser.email, @@ -209,25 +211,22 @@ app.post("/register", async (req, res) => { app.post("/login", async (req, res) => { const { email, password } = req.body; - try { const user = await User.findOne({ email }); - if (!user || !bcrypt.compareSync(password, user.password)) { return res.status(401).json({ error: "Invalid email or password" }); } - res.status(200).json({ email: user.email, id: user._id, accessToken: user.accessToken, }); - } catch (err) { + } catch { res.status(400).json({ error: "Something went wrong" }); } }); -// 🚀 Start server +// Server startup app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); From cade57ef94370d7ee8cad1aee0786461892e77d5 Mon Sep 17 00:00:00 2001 From: Oscar Liljefors Date: Thu, 24 Jul 2025 03:28:47 +0200 Subject: [PATCH 15/15] being able to post thoughts anon --- server.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/server.js b/server.js index 836e625..7bb0f50 100644 --- a/server.js +++ b/server.js @@ -114,18 +114,20 @@ app.get("/thoughts/:id", async (req, res) => { app.post("/thoughts", async (req, res) => { const { message } = req.body; - let createdBy = null; - - const token = req.header("Authorization"); - if (token) { - const user = await User.findOne({ accessToken: token }); - if (user) createdBy = user._id; - } + const accessToken = req.header("Authorization"); try { - const thought = new Thought({ message, createdBy }); - const saved = await thought.save(); - res.status(201).json(saved); + let createdBy = null; + if (accessToken) { + const user = await User.findOne({ accessToken }); + if (user) { + createdBy = user._id; + } + } + + const newThought = new Thought({ message, createdBy }); + const savedThought = await newThought.save(); + res.status(201).json(savedThought); } catch (err) { res.status(400).json({ error: err.message }); }