From 0ba2f4d996f33926eb4a7c0207b561d4cfcbe991 Mon Sep 17 00:00:00 2001 From: llindallarsson Date: Thu, 12 Jun 2025 12:10:26 +0200 Subject: [PATCH 01/14] three endpoints added for /(returning express list endpoints), /thoughts, /thoughts:id --- package.json | 9 +++++---- server.js | 37 +++++++++++++++++++++++++++---------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index bf25bb6..bf000ff 100644 --- a/package.json +++ b/package.json @@ -9,11 +9,12 @@ "author": "", "license": "ISC", "dependencies": { - "@babel/core": "^7.17.9", - "@babel/node": "^7.16.8", - "@babel/preset-env": "^7.16.11", + "@babel/core": "^7.27.4", + "@babel/node": "^7.27.1", + "@babel/preset-env": "^7.27.2", "cors": "^2.8.5", "express": "^4.17.3", - "nodemon": "^3.0.1" + "express-list-endpoints": "^7.1.1", + "nodemon": "^3.1.10" } } diff --git a/server.js b/server.js index f47771b..e426c89 100644 --- a/server.js +++ b/server.js @@ -1,22 +1,39 @@ -import cors from "cors" -import express from "express" +import cors from "cors"; +import express from "express"; +import listEndpoints from "express-list-endpoints"; + +import data from "./data.json"; // Defines the port the app will run on. Defaults to 8080, but can be overridden // when starting the server. Example command to overwrite PORT env variable value: // PORT=9000 npm start -const port = process.env.PORT || 8080 -const app = express() +const port = process.env.PORT || 8080; +const app = express(); // Add middlewares to enable cors and json body parsing -app.use(cors()) -app.use(express.json()) +app.use(cors()); +app.use(express.json()); // Start defining your routes here app.get("/", (req, res) => { - res.send("Hello Technigo!") -}) + res.json(listEndpoints(app)); +}); + +app.get("/thoughts", (req, res) => { + res.json(data); +}); + +app.get("/thoughts/:id", (req, res) => { + const id = req.params.id; + const thought = data.find((thought) => thought._id === id); + if (!thought) { + res.status(404).json({ error: "Thought not found" }); + } else { + res.json(thought); + } +}); // Start the server app.listen(port, () => { - console.log(`Server running on http://localhost:${port}`) -}) + console.log(`Server running on http://localhost:${port}`); +}); From b1bcc0f582a2e460e21387fdfaa9c4cfd1b21578 Mon Sep 17 00:00:00 2001 From: llindallarsson Date: Thu, 12 Jun 2025 17:58:55 +0200 Subject: [PATCH 02/14] Mongoose added, errors handling, update and delete a thought --- package.json | 2 + server.js | 143 ++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 126 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index bf000ff..e7c254c 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,10 @@ "@babel/node": "^7.27.1", "@babel/preset-env": "^7.27.2", "cors": "^2.8.5", + "dotenv": "^16.5.0", "express": "^4.17.3", "express-list-endpoints": "^7.1.1", + "mongoose": "^8.15.1", "nodemon": "^3.1.10" } } diff --git a/server.js b/server.js index e426c89..2df1885 100644 --- a/server.js +++ b/server.js @@ -1,39 +1,144 @@ import cors from "cors"; +import dotenv from "dotenv"; import express from "express"; -import listEndpoints from "express-list-endpoints"; +import mongoose from "mongoose"; -import data from "./data.json"; +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: -// 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()); -// Start defining your routes here -app.get("/", (req, res) => { - res.json(listEndpoints(app)); +const mongoURL = process.env.MONGO_URL || "mongodb://localhost/happyThoughts"; +mongoose.connect(mongoURL, { useNewUrlParser: true, useUnifiedTopology: true }); +mongoose.Promise = Promise; + +const HappyThought = mongoose.model( + "HappyThought", + new mongoose.Schema( + { + message: { + type: String, + required: true, + minlength: 5, + maxlength: 140, + }, + hearts: { + type: Number, + default: 0, + }, + likedBy: { + type: [String], + default: [], + }, + }, + { + timestamps: true, + } + ) +); + +// ⚠️ Kommentera bort om du inte vill rensa databasen varje gång +/* +HappyThought.deleteMany().then(() => { + new HappyThought({ message: "Hejsan", hearts: 2 }).save(); + new HappyThought({ message: "Tjaba tjena", hearts: 6 }).save(); + new HappyThought({ message: "Jag älskar smör", hearts: 0 }).save(); +}); +*/ + +app.get("/", async (req, res) => { + const thoughts = await HappyThought.find(); + res.json(thoughts); +}); + +app.get("/thoughts", async (req, res) => { + try { + const thoughts = await HappyThought.find() + .sort({ createdAt: -1 }) + .limit(20); + res.status(200).json(thoughts); + } catch (error) { + res.status(500).json({ error: "Could not fetch thoughts" }); + } +}); + +app.post("/thoughts", async (req, res) => { + const { message } = req.body; + + try { + const newThought = new HappyThought({ message }); + const savedThought = await newThought.save(); + res.status(201).json(savedThought); + } catch (error) { + res.status(400).json({ error: "Invalid input", details: error.errors }); + } +}); + +app.get("/thoughts/:id", async (req, res) => { + try { + const thought = await HappyThought.findById(req.params.id); + if (thought) { + res.json(thought); + } else { + res.status(404).json({ error: "Thought not found" }); + } + } catch (error) { + res.status(400).json({ error: "Invalid ID format" }); + } +}); + +app.put("/thoughts/:id", async (req, res) => { + try { + const updatedThought = await HappyThought.findByIdAndUpdate( + req.params.id, + req.body, + { new: true, runValidators: true } + ); + + if (updatedThought) { + res.status(200).json(updatedThought); + } else { + res.status(404).json({ error: "Thought not found" }); + } + } catch (error) { + res.status(400).json({ error: "Invalid update", details: error.errors }); + } }); -app.get("/thoughts", (req, res) => { - res.json(data); +app.delete("/thoughts/:id", async (req, res) => { + try { + const deleted = await HappyThought.findByIdAndDelete(req.params.id); + if (deleted) { + res.status(200).json({ success: true }); + } else { + res.status(404).json({ error: "Thought not found" }); + } + } catch (error) { + res.status(400).json({ error: "Invalid ID format" }); + } }); -app.get("/thoughts/:id", (req, res) => { - const id = req.params.id; - const thought = data.find((thought) => thought._id === id); - if (!thought) { - res.status(404).json({ error: "Thought not found" }); - } else { - res.json(thought); +app.post("/thoughts/:id/like", async (req, res) => { + try { + const thought = await HappyThought.findByIdAndUpdate( + req.params.id, + { $inc: { hearts: 1 } }, + { new: true } + ); + + if (thought) { + res.status(200).json(thought); + } else { + res.status(404).json({ error: "Thought not found" }); + } + } catch (error) { + res.status(400).json({ error: "Invalid ID format" }); } }); -// Start the server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); From 6f2a10b4402cc240a28ed6ee6a6cc0b8d7b310ef Mon Sep 17 00:00:00 2001 From: llindallarsson Date: Thu, 12 Jun 2025 18:01:14 +0200 Subject: [PATCH 03/14] bcrypt installed --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index e7c254c..ac72161 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@babel/core": "^7.27.4", "@babel/node": "^7.27.1", "@babel/preset-env": "^7.27.2", + "bcrypt": "^6.0.0", "cors": "^2.8.5", "dotenv": "^16.5.0", "express": "^4.17.3", From e53c0f41fc33f6a33b5384a057e4ea6ddf3fea5e Mon Sep 17 00:00:00 2001 From: llindallarsson Date: Thu, 12 Jun 2025 18:03:22 +0200 Subject: [PATCH 04/14] added user-model --- models/User.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 models/User.js diff --git a/models/User.js b/models/User.js new file mode 100644 index 0000000..e69de29 From cee1c0d6212f59870548e212bd9fbd42b453e7d8 Mon Sep 17 00:00:00 2001 From: llindallarsson Date: Thu, 12 Jun 2025 18:34:33 +0200 Subject: [PATCH 05/14] cleaned and stucture the code --- app.js | 0 models/Thought.js | 24 ++++++++ models/User.js | 23 ++++++++ routes/thoughts.js | 0 routes/users.js | 0 server.js | 139 +-------------------------------------------- 6 files changed, 48 insertions(+), 138 deletions(-) create mode 100644 app.js create mode 100644 models/Thought.js create mode 100644 routes/thoughts.js create mode 100644 routes/users.js diff --git a/app.js b/app.js new file mode 100644 index 0000000..e69de29 diff --git a/models/Thought.js b/models/Thought.js new file mode 100644 index 0000000..27dba6f --- /dev/null +++ b/models/Thought.js @@ -0,0 +1,24 @@ +import mongoose from "mongoose"; + +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 cannot exceed 140 characters"], + trim: true, + }, + hearts: { + type: Number, + default: 0, + }, + }, + { + timestamps: true, // Skapar createdAt och updatedAt automatiskt + } +); + +const Thought = mongoose.model("Thought", thoughtSchema); + +export default Thought; diff --git a/models/User.js b/models/User.js index e69de29..8d51124 100644 --- a/models/User.js +++ b/models/User.js @@ -0,0 +1,23 @@ +import mongoose from "mongoose"; +import bcrypt from "bcrypt"; + +const UserSchema = new mongoose.Schema({ + username: { + type: String, + unique: true, + required: true, + minlength: 3, + }, + password: { + type: String, + required: true, + minlength: 5, + }, +}); + +// Kryptera lösenord innan det sparas +UserSchema.pre("save", async function () { + this.password = await bcrypt.hash(this.password, 10); +}); + +export const User = mongoose.model("User", UserSchema); diff --git a/routes/thoughts.js b/routes/thoughts.js new file mode 100644 index 0000000..e69de29 diff --git a/routes/users.js b/routes/users.js new file mode 100644 index 0000000..e69de29 diff --git a/server.js b/server.js index 2df1885..3ddaec1 100644 --- a/server.js +++ b/server.js @@ -1,143 +1,6 @@ -import cors from "cors"; -import dotenv from "dotenv"; -import express from "express"; -import mongoose from "mongoose"; - -dotenv.config(); +import app from "./app.js"; const port = process.env.PORT || 8080; -const app = express(); - -app.use(cors()); -app.use(express.json()); - -const mongoURL = process.env.MONGO_URL || "mongodb://localhost/happyThoughts"; -mongoose.connect(mongoURL, { useNewUrlParser: true, useUnifiedTopology: true }); -mongoose.Promise = Promise; - -const HappyThought = mongoose.model( - "HappyThought", - new mongoose.Schema( - { - message: { - type: String, - required: true, - minlength: 5, - maxlength: 140, - }, - hearts: { - type: Number, - default: 0, - }, - likedBy: { - type: [String], - default: [], - }, - }, - { - timestamps: true, - } - ) -); - -// ⚠️ Kommentera bort om du inte vill rensa databasen varje gång -/* -HappyThought.deleteMany().then(() => { - new HappyThought({ message: "Hejsan", hearts: 2 }).save(); - new HappyThought({ message: "Tjaba tjena", hearts: 6 }).save(); - new HappyThought({ message: "Jag älskar smör", hearts: 0 }).save(); -}); -*/ - -app.get("/", async (req, res) => { - const thoughts = await HappyThought.find(); - res.json(thoughts); -}); - -app.get("/thoughts", async (req, res) => { - try { - const thoughts = await HappyThought.find() - .sort({ createdAt: -1 }) - .limit(20); - res.status(200).json(thoughts); - } catch (error) { - res.status(500).json({ error: "Could not fetch thoughts" }); - } -}); - -app.post("/thoughts", async (req, res) => { - const { message } = req.body; - - try { - const newThought = new HappyThought({ message }); - const savedThought = await newThought.save(); - res.status(201).json(savedThought); - } catch (error) { - res.status(400).json({ error: "Invalid input", details: error.errors }); - } -}); - -app.get("/thoughts/:id", async (req, res) => { - try { - const thought = await HappyThought.findById(req.params.id); - if (thought) { - res.json(thought); - } else { - res.status(404).json({ error: "Thought not found" }); - } - } catch (error) { - res.status(400).json({ error: "Invalid ID format" }); - } -}); - -app.put("/thoughts/:id", async (req, res) => { - try { - const updatedThought = await HappyThought.findByIdAndUpdate( - req.params.id, - req.body, - { new: true, runValidators: true } - ); - - if (updatedThought) { - res.status(200).json(updatedThought); - } else { - res.status(404).json({ error: "Thought not found" }); - } - } catch (error) { - res.status(400).json({ error: "Invalid update", details: error.errors }); - } -}); - -app.delete("/thoughts/:id", async (req, res) => { - try { - const deleted = await HappyThought.findByIdAndDelete(req.params.id); - if (deleted) { - res.status(200).json({ success: true }); - } else { - res.status(404).json({ error: "Thought not found" }); - } - } catch (error) { - res.status(400).json({ error: "Invalid ID format" }); - } -}); - -app.post("/thoughts/:id/like", async (req, res) => { - try { - const thought = await HappyThought.findByIdAndUpdate( - req.params.id, - { $inc: { hearts: 1 } }, - { new: true } - ); - - if (thought) { - res.status(200).json(thought); - } else { - res.status(404).json({ error: "Thought not found" }); - } - } catch (error) { - res.status(400).json({ error: "Invalid ID format" }); - } -}); app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); From de74bae882df92b5db5dd6036c8c63ef966d2042 Mon Sep 17 00:00:00 2001 From: llindallarsson Date: Mon, 16 Jun 2025 14:23:24 +0200 Subject: [PATCH 06/14] Fix user signup with bcrypt password hashing --- app.js | 33 +++++++++ routes/users.js => middleware/auth.js | 0 models/User.js | 16 ++--- package.json | 1 + routes/auth.js | 58 ++++++++++++++++ routes/thoughts.js | 97 +++++++++++++++++++++++++++ 6 files changed, 197 insertions(+), 8 deletions(-) rename routes/users.js => middleware/auth.js (100%) create mode 100644 routes/auth.js diff --git a/app.js b/app.js index e69de29..59cf918 100644 --- a/app.js +++ b/app.js @@ -0,0 +1,33 @@ +import express from "express"; +import cors from "cors"; +import mongoose from "mongoose"; +import dotenv from "dotenv"; + +import thoughtRoutes from "./routes/thoughts.js"; +import userRoutes from "./routes/auth.js"; + +dotenv.config(); + +const app = express(); + +// === Middleware === +app.use(cors()); +app.use(express.json()); + +// === Connect to MongoDB === +const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/happyThoughts"; +mongoose.connect(mongoUrl, { + useNewUrlParser: true, + useUnifiedTopology: true, +}); +mongoose.Promise = Promise; + +// === Routes === +app.get("/", (req, res) => { + res.send("Welcome to the Happy Thoughts API!"); +}); + +app.use("/thoughts", thoughtRoutes); +app.use("/users", userRoutes); + +export default app; diff --git a/routes/users.js b/middleware/auth.js similarity index 100% rename from routes/users.js rename to middleware/auth.js diff --git a/models/User.js b/models/User.js index 8d51124..355a5b6 100644 --- a/models/User.js +++ b/models/User.js @@ -1,23 +1,23 @@ import mongoose from "mongoose"; -import bcrypt from "bcrypt"; +import crypto from "crypto"; -const UserSchema = new mongoose.Schema({ +const userSchema = new mongoose.Schema({ username: { type: String, unique: true, required: true, minlength: 3, + maxlength: 30, }, password: { type: String, required: true, minlength: 5, }, + accessToken: { + type: String, + default: () => crypto.randomBytes(128).toString("hex"), + }, }); -// Kryptera lösenord innan det sparas -UserSchema.pre("save", async function () { - this.password = await bcrypt.hash(this.password, 10); -}); - -export const User = mongoose.model("User", UserSchema); +export const User = mongoose.model("User", userSchema); diff --git a/package.json b/package.json index ac72161..864d8ac 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@babel/node": "^7.27.1", "@babel/preset-env": "^7.27.2", "bcrypt": "^6.0.0", + "bcrypt-nodejs": "^0.0.3", "cors": "^2.8.5", "dotenv": "^16.5.0", "express": "^4.17.3", diff --git a/routes/auth.js b/routes/auth.js new file mode 100644 index 0000000..214455c --- /dev/null +++ b/routes/auth.js @@ -0,0 +1,58 @@ +import express from "express"; +import bcrypt from "bcrypt"; +import crypto from "crypto"; + +import { User } from "../models/User.js"; + +const router = express.Router(); + +router.post("/", async (req, res) => { + console.log("Incoming body:", req.body); + const { username, password } = req.body; + + try { + const salt = bcrypt.genSaltSync(); + const hashedPassword = bcrypt.hashSync(password, salt); + const user = new User({ + username, + password: hashedPassword, + accessToken: crypto.randomBytes(128).toString("hex"), + }); + + await user.save(); + + res.status(201).json({ + userId: user._id, + username: user.username, + accessToken: user.accessToken, + }); + } catch (err) { + console.error("Signup error:", err); // ← Lägg till denna rad + + if (err.code === 11000) { + res.status(400).json({ message: "Username already exists" }); + } else { + res.status(400).json({ message: "Invalid request", error: err }); + } + } +}); + +router.post("/sessions", async (req, res) => { + const { username, password } = req.body; + + try { + const user = await User.findOne({ username }); + if (user && bcrypt.compareSync(password, user.password)) { + res.json({ + userId: user._id, + accessToken: user.accessToken, + }); + } else { + res.status(401).json({ error: "Username or password is incorrect" }); + } + } catch (err) { + res.status(500).json({ error: "Internal server error" }); + } +}); + +export default router; diff --git a/routes/thoughts.js b/routes/thoughts.js index e69de29..f35a9bc 100644 --- a/routes/thoughts.js +++ b/routes/thoughts.js @@ -0,0 +1,97 @@ +import express from "express"; +import HappyThought from "../models/Thought.js"; + +const router = express.Router(); + +// get last 20 posts +router.get("/", async (req, res) => { + try { + const thoughts = await HappyThought.find() + .sort({ createdAt: -1 }) + .limit(20); + res.status(200).json(thoughts); + } catch (error) { + res.status(500).json({ error: "Could not fetch thoughts" }); + } +}); + +// get single post +router.get("/:id", async (req, res) => { + try { + const thought = await HappyThought.findById(req.params.id); + if (thought) { + res.json(thought); + } else { + res.status(404).json({ error: "Thought not found" }); + } + } catch (error) { + res.status(400).json({ error: "Invalid ID format" }); + } +}); + +// add new post +router.post("/", async (req, res) => { + const { message } = req.body; + + try { + const newThought = new HappyThought({ message }); + const savedThought = await newThought.save(); + res.status(201).json(savedThought); + } catch (error) { + res.status(400).json({ error: "Invalid input", details: error.errors }); + } +}); + +// update existing post +router.put("/:id", async (req, res) => { + try { + const updatedThought = await HappyThought.findByIdAndUpdate( + req.params.id, + req.body, + { new: true, runValidators: true } + ); + + if (updatedThought) { + res.status(200).json(updatedThought); + } else { + res.status(404).json({ error: "Thought not found" }); + } + } catch (error) { + res.status(400).json({ error: "Invalid update", details: error.errors }); + } +}); + +// delete post +router.delete("/:id", async (req, res) => { + try { + const deleted = await HappyThought.findByIdAndDelete(req.params.id); + if (deleted) { + res.status(200).json({ success: true }); + } else { + res.status(404).json({ error: "Thought not found" }); + } + } catch (error) { + res.status(400).json({ error: "Invalid ID format" }); + } +}); + +// like post +router.post("/thoughts/:id/like", async (req, res) => { + try { + const thought = await HappyThought.findByIdAndUpdate( + req.params.id, + { $inc: { hearts: 1 } }, + { new: true } + ); + + if (thought) { + res.status(200).json(thought); + } else { + res.status(404).json({ error: "Thought not found" }); + } + } catch (error) { + res.status(400).json({ error: "Invalid ID format" }); + } +}); + +export default router; From 6ebd8caf208b5e300f93ecac92f0700a764b36f2 Mon Sep 17 00:00:00 2001 From: llindallarsson Date: Fri, 27 Jun 2025 09:59:17 +0200 Subject: [PATCH 07/14] changed host --- server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index 3ddaec1..2f28ec0 100644 --- a/server.js +++ b/server.js @@ -2,6 +2,6 @@ import app from "./app.js"; const port = process.env.PORT || 8080; -app.listen(port, () => { - console.log(`Server running on http://localhost:${port}`); +app.listen(port, "0.0.0.0", () => { + console.log(`Server running on http://0.0.0.0:${port}`); }); From e84f46b552a6ea50b8bd8f97027d90e2369cf296 Mon Sep 17 00:00:00 2001 From: llindallarsson Date: Wed, 6 Aug 2025 14:12:34 +0200 Subject: [PATCH 08/14] added authenicate for user --- middleware/auth.js | 0 middlewares/authenticateUser.js | 19 +++++++++++++++++++ models/Thought.js | 9 ++++++++- routes/thoughts.js | 12 +++++++++--- 4 files changed, 36 insertions(+), 4 deletions(-) delete mode 100644 middleware/auth.js create mode 100644 middlewares/authenticateUser.js diff --git a/middleware/auth.js b/middleware/auth.js deleted file mode 100644 index e69de29..0000000 diff --git a/middlewares/authenticateUser.js b/middlewares/authenticateUser.js new file mode 100644 index 0000000..4d0dc0f --- /dev/null +++ b/middlewares/authenticateUser.js @@ -0,0 +1,19 @@ +import { User } from "../models/User.js"; + +const authenticateUser = async (req, res, next) => { + const accessToken = req.header("Authorization"); + + try { + const user = await User.findOne({ accessToken }); + if (user) { + req.user = user; // Lägg till användaren på request-objektet + next(); // Gå vidare till nästa steg (ex. route handler) + } else { + res.status(401).json({ message: "Access token is invalid" }); + } + } catch (error) { + res.status(500).json({ message: "Server error during authentication" }); + } +}; + +export default authenticateUser; diff --git a/models/Thought.js b/models/Thought.js index 27dba6f..e9ca4b9 100644 --- a/models/Thought.js +++ b/models/Thought.js @@ -15,7 +15,14 @@ const thoughtSchema = new mongoose.Schema( }, }, { - timestamps: true, // Skapar createdAt och updatedAt automatiskt + timestamps: true, + }, + { + createdBy: { + type: mongoose.Schema.Types.ObjectId, + ref: "User", + required: true, + }, } ); diff --git a/routes/thoughts.js b/routes/thoughts.js index f35a9bc..ac650df 100644 --- a/routes/thoughts.js +++ b/routes/thoughts.js @@ -1,4 +1,6 @@ import express from "express"; + +import authenticateUser from "../middlewares/authenticateUser.js"; import HappyThought from "../models/Thought.js"; const router = express.Router(); @@ -30,11 +32,15 @@ router.get("/:id", async (req, res) => { }); // add new post -router.post("/", async (req, res) => { +router.post("/", authenticateUser, async (req, res) => { const { message } = req.body; try { - const newThought = new HappyThought({ message }); + const newThought = new HappyThought({ + message, + createdBy: req.user._id, // lägg till användaren som skapar + }); + const savedThought = await newThought.save(); res.status(201).json(savedThought); } catch (error) { @@ -76,7 +82,7 @@ router.delete("/:id", async (req, res) => { }); // like post -router.post("/thoughts/:id/like", async (req, res) => { +router.post("/:id/like", async (req, res) => { try { const thought = await HappyThought.findByIdAndUpdate( req.params.id, From bd710c1417619792cc672c9dcb4e60c6979725fd Mon Sep 17 00:00:00 2001 From: Linda Larsson Date: Mon, 6 Oct 2025 13:29:42 +0200 Subject: [PATCH 09/14] Add npm express-list-endpoints package --- README.md | 39 ++++++++++++++++++++++++++++----- app.js | 11 ++++------ middlewares/authenticateUser.js | 4 ++-- routes/auth.js | 5 ++--- server.js | 13 ++++++++--- 5 files changed, 51 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 0f9f073..fc6f271 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,38 @@ -# Project API +# Happy Thoughts API -This project includes the packages and babel setup for an express server, and is just meant to make things a little simpler to get up and running with. +A RESTful API for sharing and managing happy thoughts with user authentication. -## Getting started +## API Endpoints -Install dependencies with `npm install`, then start the server by running `npm run dev` +### Root -## View it live +`GET /` +Welcome message for the API. -Every project should be deployed somewhere. Be sure to include the link to the deployed project so that the viewer can click around and see what it's all about. +### Thoughts + +`GET /thoughts` +Retrieves all thoughts. + +`POST /thoughts` +Creates a new thought. + +`GET /thoughts/:id` +Retrieves a specific thought by ID. + +`PUT /thoughts/:id` +Updates a specific thought. + +`DELETE /thoughts/:id` +Deletes a specific thought. + +`POST /thoughts/:id/like` +Adds a like (heart) to a specific thought. + +### Users + +`POST /users` +Registers a new user. + +`POST /users/sessions` +Logs in an existing user. diff --git a/app.js b/app.js index 59cf918..b1aa08b 100644 --- a/app.js +++ b/app.js @@ -1,10 +1,10 @@ -import express from "express"; import cors from "cors"; -import mongoose from "mongoose"; import dotenv from "dotenv"; +import express from "express"; +import mongoose from "mongoose"; -import thoughtRoutes from "./routes/thoughts.js"; import userRoutes from "./routes/auth.js"; +import thoughtRoutes from "./routes/thoughts.js"; dotenv.config(); @@ -16,10 +16,7 @@ app.use(express.json()); // === Connect to MongoDB === const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/happyThoughts"; -mongoose.connect(mongoUrl, { - useNewUrlParser: true, - useUnifiedTopology: true, -}); +mongoose.connect(mongoUrl); mongoose.Promise = Promise; // === Routes === diff --git a/middlewares/authenticateUser.js b/middlewares/authenticateUser.js index 4d0dc0f..d47e321 100644 --- a/middlewares/authenticateUser.js +++ b/middlewares/authenticateUser.js @@ -6,8 +6,8 @@ const authenticateUser = async (req, res, next) => { try { const user = await User.findOne({ accessToken }); if (user) { - req.user = user; // Lägg till användaren på request-objektet - next(); // Gå vidare till nästa steg (ex. route handler) + req.user = user; + next(); } else { res.status(401).json({ message: "Access token is invalid" }); } diff --git a/routes/auth.js b/routes/auth.js index 214455c..f70a873 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -1,6 +1,6 @@ -import express from "express"; import bcrypt from "bcrypt"; import crypto from "crypto"; +import express from "express"; import { User } from "../models/User.js"; @@ -27,8 +27,7 @@ router.post("/", async (req, res) => { accessToken: user.accessToken, }); } catch (err) { - console.error("Signup error:", err); // ← Lägg till denna rad - + console.error("Signup error:", err); if (err.code === 11000) { res.status(400).json({ message: "Username already exists" }); } else { diff --git a/server.js b/server.js index 2f28ec0..15adb01 100644 --- a/server.js +++ b/server.js @@ -1,7 +1,14 @@ +import listEndpoints from "express-list-endpoints"; + import app from "./app.js"; -const port = process.env.PORT || 8080; +const PORT = process.env.PORT || 8081; + +app.listen(PORT, () => { + console.log(`🚀 Server running on http://localhost:${PORT}`); + console.log(`📝 Thoughts API: http://localhost:${PORT}/thoughts`); + console.log(`👤 Users API: http://localhost:${PORT}/users`); -app.listen(port, "0.0.0.0", () => { - console.log(`Server running on http://0.0.0.0:${port}`); + console.log("\n📋 All API Endpoints:"); + console.log(listEndpoints(app)); }); From 785382b1e79d53cc9f8c5cc86fbdf95dde533da7 Mon Sep 17 00:00:00 2001 From: Linda Larsson Date: Mon, 6 Oct 2025 18:42:42 +0200 Subject: [PATCH 10/14] Fix authenticateUser --- app.js | 20 +++++++++++++++++++- middlewares/authenticateUser.js | 4 +++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index b1aa08b..1b3a237 100644 --- a/app.js +++ b/app.js @@ -10,8 +10,26 @@ dotenv.config(); const app = express(); +const allowedOrigins = [ + "http://localhost:5173", + "https://happythoughtproject.netlify.app", +]; + // === Middleware === -app.use(cors()); +app.use( + cors({ + origin: (origin, callback) => { + if (!origin) return callback(null, true); + + if (allowedOrigins.includes(origin)) { + callback(null, true); + } else { + callback(new Error("Not allowed by CORS")); + } + }, + credentials: true, + }) +); app.use(express.json()); // === Connect to MongoDB === diff --git a/middlewares/authenticateUser.js b/middlewares/authenticateUser.js index d47e321..5c0ecf9 100644 --- a/middlewares/authenticateUser.js +++ b/middlewares/authenticateUser.js @@ -1,7 +1,9 @@ import { User } from "../models/User.js"; const authenticateUser = async (req, res, next) => { - const accessToken = req.header("Authorization"); + const authHeader = req.header("Authorization"); + + const accessToken = authHeader?.replace("Bearer ", ""); try { const user = await User.findOne({ accessToken }); From 635984b2e54859811055714756b69ad21feecf45 Mon Sep 17 00:00:00 2001 From: Linda Larsson Date: Tue, 18 Nov 2025 13:21:00 +0100 Subject: [PATCH 11/14] fixed email --- routes/auth.js | 1 + 1 file changed, 1 insertion(+) diff --git a/routes/auth.js b/routes/auth.js index f70a873..54ae031 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -17,6 +17,7 @@ router.post("/", async (req, res) => { username, password: hashedPassword, accessToken: crypto.randomBytes(128).toString("hex"), + email: new Date().getTime(), }); await user.save(); From bd46f58e726f0438c853c5e7f2965061e49985d8 Mon Sep 17 00:00:00 2001 From: Linda Larsson Date: Tue, 18 Nov 2025 13:41:56 +0100 Subject: [PATCH 12/14] trying to log db schema for users --- app.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app.js b/app.js index 1b3a237..ca3d0b0 100644 --- a/app.js +++ b/app.js @@ -37,6 +37,8 @@ const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/happyThoughts"; mongoose.connect(mongoUrl); mongoose.Promise = Promise; +console.log(mongoose.model("users").schema); + // === Routes === app.get("/", (req, res) => { res.send("Welcome to the Happy Thoughts API!"); From e93c0edc05a8a9ee4d1fac5627c5da6bed03a25a Mon Sep 17 00:00:00 2001 From: Linda Larsson Date: Tue, 18 Nov 2025 13:47:38 +0100 Subject: [PATCH 13/14] still trying to fix email --- app.js | 2 -- models/User.js | 8 +++++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app.js b/app.js index ca3d0b0..1b3a237 100644 --- a/app.js +++ b/app.js @@ -37,8 +37,6 @@ const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/happyThoughts"; mongoose.connect(mongoUrl); mongoose.Promise = Promise; -console.log(mongoose.model("users").schema); - // === Routes === app.get("/", (req, res) => { res.send("Welcome to the Happy Thoughts API!"); diff --git a/models/User.js b/models/User.js index 355a5b6..0151c36 100644 --- a/models/User.js +++ b/models/User.js @@ -1,5 +1,5 @@ -import mongoose from "mongoose"; import crypto from "crypto"; +import mongoose from "mongoose"; const userSchema = new mongoose.Schema({ username: { @@ -18,6 +18,12 @@ const userSchema = new mongoose.Schema({ type: String, default: () => crypto.randomBytes(128).toString("hex"), }, + email: { + type: String, + unique: true, + required: true, + default: () => crypto.randomBytes(10).toString("hex"), + }, }); export const User = mongoose.model("User", userSchema); From b548f791100198ab2d653e61e090e6b6e2891b1b Mon Sep 17 00:00:00 2001 From: Linda Larsson Date: Tue, 18 Nov 2025 14:49:19 +0100 Subject: [PATCH 14/14] add username: user.username, --- routes/auth.js | 1 + 1 file changed, 1 insertion(+) diff --git a/routes/auth.js b/routes/auth.js index 54ae031..284cf59 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -44,6 +44,7 @@ router.post("/sessions", async (req, res) => { const user = await User.findOne({ username }); if (user && bcrypt.compareSync(password, user.password)) { res.json({ + username: user.username, userId: user._id, accessToken: user.accessToken, });