From 7d34c9dc42b446515c261dcbb7a3599b2a037d7c Mon Sep 17 00:00:00 2001 From: oskarnordin Date: Wed, 4 Jun 2025 20:31:51 +0200 Subject: [PATCH 01/15] Connect to postman connect to postman with get/post --- package.json | 1 + server.js | 113 ++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 103 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index bf25bb6..c38890c 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", + "mongoose": "^8.15.1", "nodemon": "^3.0.1" } } diff --git a/server.js b/server.js index f47771b..5284a9e 100644 --- a/server.js +++ b/server.js @@ -1,22 +1,113 @@ -import cors from "cors" -import express from "express" +import cors from 'cors'; +import express from 'express'; +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 app = express() +const port = process.env.PORT || 8080; +const app = express(); + +const happythoughtsData = JSON.parse(fs.readFileSync('./data.json', 'utf-8')); + +// Mongoose schema +const happyThoughtsSchema = new mongoose.Schema({ + message: { type: String, required: true }, + hearts: { type: Number, default: 0 }, + createdAt: { type: Date, default: Date.now }, +}); + +// Create Mongoose model +const HappyThoughts = mongoose.model('HappyThoughts', happyThoughtsSchema); + +// Connect to MongoDB +mongoose.connect(process.env.MONGO_URL || 'mongodb://localhost/happythoughts'); +mongoose.Promise = Promise; + +// RESET_DB logic +if (process.env.RESET_DB) { + const seedDatabase = async () => { + console.log('Resetting database!'); + await HappyThoughts.deleteMany(); + + for (const item of happythoughtsData) { + const newThought = new HappyThoughts(item); + await newThought.save(); + } + }; + + seedDatabase(); // ✅ calling the async function +} // 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!") -}) +app.get('/', (req, res) => { + res.send('Hello Technigo!'); +}); + +//return all thoughts +app.get('/thoughts', async (req, res) => { + const thoughts = await HappyThoughts.find().sort({ createdAt: -1 }).limit(20); + res.json(thoughts); +}); + +// return a random thought (has to be placed before the id route) +app.get('/thoughts/random', (req, res) => { + const randomIndex = Math.floor(Math.random() * happythoughtsData.length); + const randomThought = happythoughtsData[randomIndex]; + + res.json(randomThought); +}); + +//return all thoughts sorted by likes (most likes on top)(has to be placed before the id route) +app.get('/thoughts/likes', (req, res) => { + const sortedThoughts = [...happythoughtsData].sort( + (a, b) => b.hearts - a.hearts + ); + res.json(sortedThoughts); +}); + +// return a specific thought by id +app.get('/thoughts/:id', (req, res) => { + const { id } = req.params; + + const thought = happythoughtsData.find((thought) => thought._id === +id); + + if (thought) { + res.json(thought); + } else { + res.status(404).json({ error: 'No thoughts' }); + } +}); + +app.post('/thoughts', async (req, res) => { + try { + const { message } = req.body; + + // Basic manual validation (mongoose also validates) + if (!message) { + return res.status(400).json({ error: 'Thought is required' }); + } + + const newThought = new HappyThoughts({ + message, + hearts: 0, + createdAt: new Date(), + }); + const savedThought = await newThought.save(); + + res.status(201).json(savedThought); + } catch (error) { + console.error('Error saving thought:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); // Start the server app.listen(port, () => { - console.log(`Server running on http://localhost:${port}`) -}) + console.log(`Server running on http://localhost:${port}`); +}); From a8cdf9154c860e0b5a08b84947cc74c1519cab0c Mon Sep 17 00:00:00 2001 From: Sofie Date: Tue, 10 Jun 2025 09:15:05 +0200 Subject: [PATCH 02/15] readme --- README.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/README.md b/README.md index 0f9f073..4fe7d9b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,3 @@ # Project 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. - -## Getting started - -Install dependencies with `npm install`, then start the server by running `npm run dev` - -## View it live - -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. +backend happy api \ No newline at end of file From 4c23a26fe4819043bb5c66de6b6c944820ed2266 Mon Sep 17 00:00:00 2001 From: oskarnordin Date: Tue, 10 Jun 2025 09:23:10 +0200 Subject: [PATCH 03/15] Connect to frontend Connect to frontend --- server.js | 84 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 63 insertions(+), 21 deletions(-) diff --git a/server.js b/server.js index 5284a9e..1f0c9af 100644 --- a/server.js +++ b/server.js @@ -51,36 +51,62 @@ app.get('/', (req, res) => { //return all thoughts app.get('/thoughts', async (req, res) => { - const thoughts = await HappyThoughts.find().sort({ createdAt: -1 }).limit(20); - res.json(thoughts); + try { + const thoughts = await HappyThoughts.find() + .sort({ createdAt: -1 }) + .limit(20); + if (thoughts.length > 0) { + return res.json(thoughts); + } else { + res.status(404).json({ error: 'No thoughts available' }); + } + } catch (error) { + res.status(400).json({ error: 'Invalid request' }); + } }); // return a random thought (has to be placed before the id route) -app.get('/thoughts/random', (req, res) => { - const randomIndex = Math.floor(Math.random() * happythoughtsData.length); - const randomThought = happythoughtsData[randomIndex]; +app.get('/thoughts/random', async (req, res) => { + try { + const count = await HappyThoughts.countDocuments(); + if (count === 0) { + return res.status(404).json({ error: 'No thoughts' }); + } + const random = Math.floor(Math.random() * count); + const randomThought = await HappyThoughts.findOne().skip(random); - res.json(randomThought); + res.json(randomThought); + } catch (error) { + res.status(400).json({ error: 'Invalid request' }); + } }); //return all thoughts sorted by likes (most likes on top)(has to be placed before the id route) -app.get('/thoughts/likes', (req, res) => { - const sortedThoughts = [...happythoughtsData].sort( - (a, b) => b.hearts - a.hearts - ); - res.json(sortedThoughts); +app.get('/thoughts/likes', async (req, res) => { + try { + const sortedThoughts = await HappyThoughts.find().sort({ hearts: -1 }); + if (sortedThoughts.length > 0) { + res.json(sortedThoughts); + } else { + res.status(404).json({ error: 'No thoughts' }); + } + } catch (error) { + res.status(400).json({ error: 'Invalid request' }); + } }); // return a specific thought by id -app.get('/thoughts/:id', (req, res) => { +app.get('/thoughts/:id', async (req, res) => { const { id } = req.params; - - const thought = happythoughtsData.find((thought) => thought._id === +id); - - if (thought) { - res.json(thought); - } else { - res.status(404).json({ error: 'No thoughts' }); + try { + const thought = await HappyThoughts.findById(id); + if (thought) { + res.json(thought); + } else { + res.status(404).json({ error: 'No thoughts' }); + } + } catch (error) { + res.status(400).json({ error: 'Invalid ID format' }); } }); @@ -90,7 +116,9 @@ app.post('/thoughts', async (req, res) => { // Basic manual validation (mongoose also validates) if (!message) { - return res.status(400).json({ error: 'Thought is required' }); + return res + .status(400) + .json({ error: 'Your thought is invalid, please try again.' }); } const newThought = new HappyThoughts({ @@ -107,7 +135,21 @@ app.post('/thoughts', async (req, res) => { } }); -// Start the server +app.delete('/thoughts/:id', async (req, res) => { + const { id } = req.params; + try { + const deletedThought = await HappyThoughts.findByIdAndDelete(id); + if (deletedThought) { + res.json({ message: 'Thought deleted', thought: deletedThought }); + } else { + res.status(404).json({ error: 'Thought not found' }); + } + } catch (error) { + res.status(400).json({ error: 'Invalid ID format' }); + } +}); + +// Start the servers app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); From 78bf5172b8bc09dfd5cbe6822a017f33ec71572b Mon Sep 17 00:00:00 2001 From: Sofie Date: Tue, 10 Jun 2025 09:27:39 +0200 Subject: [PATCH 04/15] readme update --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4fe7d9b..94342e5 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ # Project API -backend happy api \ No newline at end of file +backend happy api +https://smilezone77.netlify.app/ \ No newline at end of file From bf339d7ad20ece5a528dce0901d98a50c3215213 Mon Sep 17 00:00:00 2001 From: oskarnordin Date: Thu, 12 Jun 2025 10:56:28 +0200 Subject: [PATCH 05/15] Update Update --- package.json | 1 + server.js | 212 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 207 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index c38890c..ac14677 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-nodejs": "^0.0.3", "cors": "^2.8.5", "express": "^4.17.3", "mongoose": "^8.15.1", diff --git a/server.js b/server.js index 1f0c9af..056c19b 100644 --- a/server.js +++ b/server.js @@ -2,11 +2,13 @@ import cors from 'cors'; import express from 'express'; import fs from 'fs'; import mongoose from 'mongoose'; +import crypto from 'crypto'; +import bcrypt from 'bcrypt-nodejs'; // 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 || 8081; const app = express(); const happythoughtsData = JSON.parse(fs.readFileSync('./data.json', 'utf-8')); @@ -16,8 +18,43 @@ const happyThoughtsSchema = new mongoose.Schema({ message: { type: String, required: true }, hearts: { type: Number, default: 0 }, createdAt: { type: Date, default: Date.now }, + username: { type: String, required: true }, + userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, }); +const userSchema = new mongoose.Schema({ + username: { + type: String, + unique: true, + required: true, + }, + password: { + type: String, + required: true, + }, + accessToken: { + type: String, + default: () => crypto.randomBytes(128).toString('hex'), + }, +}); + +const User = mongoose.model('User', userSchema); + +const authenticateUser = async (req, res, next) => { + const user = await User.findOne({ + accessToken: req.header('Authorization'), + }); + + if (user) { + req.user = user; + next(); + } else { + res.status(401).json({ + loggedOut: true, + }); + } +}; + // Create Mongoose model const HappyThoughts = mongoose.model('HappyThoughts', happyThoughtsSchema); @@ -110,8 +147,28 @@ app.get('/thoughts/:id', async (req, res) => { } }); +app.get('/users', async (req, res) => { + try { + const users = await User.find().sort({ createdAt: -1 }); + if (users.length > 0) { + res.json(users); + } else { + res.status(404).json({ error: 'No users found' }); + } + } catch (error) { + res.status(400).json({ error: 'Invalid request' }); + } +}); + app.post('/thoughts', async (req, res) => { try { + const accessToken = req.header('Authorization'); + const user = await User.findOne({ accessToken }); + + if (!user) { + return res.status(401).json({ error: 'Unauthorized' }); + } + const { message } = req.body; // Basic manual validation (mongoose also validates) @@ -126,6 +183,7 @@ app.post('/thoughts', async (req, res) => { hearts: 0, createdAt: new Date(), }); + const savedThought = await newThought.save(); res.status(201).json(savedThought); @@ -137,18 +195,160 @@ app.post('/thoughts', async (req, res) => { app.delete('/thoughts/:id', async (req, res) => { const { id } = req.params; + + try { + // Authenticate user + const accessToken = req.header('Authorization'); + const user = await User.findOne({ accessToken }); + if (!user) { + return res.status(401).json({ error: 'Unauthorized' }); + } + + // Find the thought + const thought = await HappyThoughts.findById(id); + if (!thought) { + return res.status(404).json({ error: 'Thought not found' }); + } + + // OPTIONAL: Check if the user owns the thought (requires a user ref in HappyThoughts) + if (thought.user && thought.user.toString() !== user._id.toString()) { + return res.status(403).json({ error: 'Forbidden: Not your thought' }); + } + + await HappyThoughts.findByIdAndDelete(id); + + res.json({ message: 'Thought deleted', thought }); + } catch (error) { + console.error('Error deleting thought:', error); + res.status(400).json({ error: 'Invalid ID format or deletion error' }); + } +}); + +app.put('/thoughts/:id/', async (req, res) => { + const { id } = req.params; + try { - const deletedThought = await HappyThoughts.findByIdAndDelete(id); - if (deletedThought) { - res.json({ message: 'Thought deleted', thought: deletedThought }); + // Authenticate user + const accessToken = req.header('Authorization'); + const user = await User.findOne({ accessToken }); + if (!user) { + return res.status(401).json({ error: 'Unauthorized' }); } else { - res.status(404).json({ error: 'Thought not found' }); + // Find the thought + const thought = await HappyThoughts.findById(id); + if (!thought) { + return res.status(404).json({ error: 'Thought not found' }); + } + + // OPTIONAL: Check if the user owns the thought (requires a user ref in HappyThoughts) + if (thought.user && thought.user.toString() !== user._id.toString()) { + return res.status(403).json({ error: 'Forbidden: Not your thought' }); + } + + const { message } = req.body; + if (!message) { + return res.status(400).json({ error: 'Message is required' }); + } + + thought.message = message; + await thought.save(); + + res.json({ message: 'Thought updated', thought }); } } catch (error) { - res.status(400).json({ error: 'Invalid ID format' }); + console.error('Error updating thought:', error); + res.status(400).json({ error: 'Invalid ID format or update error' }); + } +}); + +app.post('/login', async (req, res) => { + try { + const { username, password } = req.body; + + if (!username || !password) { + return res.status(400).json({ + success: false, + message: 'Username and password are required', + }); + } + + const user = await User.findOne({ username }); + + if (!user || !bcrypt.compareSync(password, user.password)) { + return res.status(401).json({ + success: false, + message: 'Invalid username or password', + }); + } + + res.json({ + success: true, + message: 'Login successful', + id: user._id, + accessToken: user.accessToken, + }); + } catch (error) { + res.status(500).json({ + success: false, + message: 'Internal server error', + errors: error, + }); } }); +app.post('/register', async (req, res) => { + try { + const { username, password } = req.body; + + if (!username || !password) { + return res.status(400).json({ + success: false, + message: 'Username and password are required', + }); + } + + const existUsername = await User.findOne({ username }); + if (existUsername) { + return res.status(400).json({ + success: false, + message: 'Username already taken', + }); + } + + const salt = bcrypt.genSaltSync(); + const user = new User({ + username, + password: bcrypt.hashSync(password, salt), + }); + + await user.save(); + + res.status(201).json({ + success: true, + message: 'User created', + id: user._id, + accessToken: user.accessToken, + }); + } catch (error) { + if (error.code === 11000) { + // Duplicate key error from MongoDB + return res.status(400).json({ + success: false, + message: 'Username already taken', + }); + } + res.status(400).json({ + success: false, + message: 'Could not create user', + errors: error, + }); + } +}); + +app.get('/secrets', authenticateUser, (req, res) => { + res.json({ secret: 'this is a secret message.' }); +}); + // Start the servers app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); From 40ae25dc4260b8ecb7b427c9181d1cc50b372409 Mon Sep 17 00:00:00 2001 From: oskarnordin Date: Thu, 12 Jun 2025 21:01:36 +0200 Subject: [PATCH 06/15] Update backend 12/6 Update backend 12/6 --- package.json | 2 + server.js | 117 ++++++++++++++++++++++++--------------------------- 2 files changed, 57 insertions(+), 62 deletions(-) diff --git a/package.json b/package.json index ac14677..9ce8e56 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,8 @@ "bcrypt-nodejs": "^0.0.3", "cors": "^2.8.5", "express": "^4.17.3", + "jsonwebtoken": "^9.0.2", + "jwt-decode": "^4.0.0", "mongoose": "^8.15.1", "nodemon": "^3.0.1" } diff --git a/server.js b/server.js index 056c19b..1a82e54 100644 --- a/server.js +++ b/server.js @@ -5,9 +5,6 @@ import mongoose from 'mongoose'; import crypto from 'crypto'; import bcrypt from 'bcrypt-nodejs'; -// 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 || 8081; const app = express(); @@ -34,6 +31,7 @@ const userSchema = new mongoose.Schema({ }, accessToken: { type: String, + required: true, default: () => crypto.randomBytes(128).toString('hex'), }, }); @@ -41,21 +39,29 @@ const userSchema = new mongoose.Schema({ const User = mongoose.model('User', userSchema); const authenticateUser = async (req, res, next) => { - const user = await User.findOne({ - accessToken: req.header('Authorization'), - }); + const authHeader = req.headers.authorization; + if (!authHeader) { + return res.status(401).json({ message: 'No access token provided' }); + } + // Accept either "Bearer " or just "" + const accessToken = authHeader.startsWith('Bearer ') + ? authHeader.replace('Bearer ', '') + : authHeader; - if (user) { + try { + const user = await User.findOne({ accessToken }); + if (!user) { + return res + .status(401) + .json({ message: 'You need to login or sign up to post a thought' }); + } req.user = user; next(); - } else { - res.status(401).json({ - loggedOut: true, - }); + } catch (error) { + return res.status(401).json({ message: 'Invalid access token' }); } }; -// Create Mongoose model const HappyThoughts = mongoose.model('HappyThoughts', happyThoughtsSchema); // Connect to MongoDB @@ -74,7 +80,7 @@ if (process.env.RESET_DB) { } }; - seedDatabase(); // ✅ calling the async function + seedDatabase(); } // Add middlewares to enable cors and json body parsing @@ -160,18 +166,10 @@ app.get('/users', async (req, res) => { } }); -app.post('/thoughts', async (req, res) => { +app.post('/thoughts', authenticateUser, async (req, res) => { try { - const accessToken = req.header('Authorization'); - const user = await User.findOne({ accessToken }); - - if (!user) { - return res.status(401).json({ error: 'Unauthorized' }); - } - const { message } = req.body; - // Basic manual validation (mongoose also validates) if (!message) { return res .status(400) @@ -182,6 +180,8 @@ app.post('/thoughts', async (req, res) => { message, hearts: 0, createdAt: new Date(), + username: req.user.username, + userId: req.user._id, // <-- use _id instead of id }); const savedThought = await newThought.save(); @@ -193,25 +193,27 @@ app.post('/thoughts', async (req, res) => { } }); -app.delete('/thoughts/:id', async (req, res) => { +app.delete('/thoughts/:id', authenticateUser, async (req, res) => { const { id } = req.params; try { - // Authenticate user - const accessToken = req.header('Authorization'); - const user = await User.findOne({ accessToken }); - if (!user) { - return res.status(401).json({ error: 'Unauthorized' }); - } - - // Find the thought const thought = await HappyThoughts.findById(id); if (!thought) { return res.status(404).json({ error: 'Thought not found' }); } - // OPTIONAL: Check if the user owns the thought (requires a user ref in HappyThoughts) - if (thought.user && thought.user.toString() !== user._id.toString()) { + // Add this log: + console.log( + 'Thought userId:', + thought.userId, + 'Request user id:', + req.user._id + ); + + if ( + thought.userId && + thought.userId.toString() !== req.user._id.toString() + ) { return res.status(403).json({ error: 'Forbidden: Not your thought' }); } @@ -224,37 +226,29 @@ app.delete('/thoughts/:id', async (req, res) => { } }); -app.put('/thoughts/:id/', async (req, res) => { +app.put('/thoughts/:id/', authenticateUser, async (req, res) => { const { id } = req.params; try { - // Authenticate user - const accessToken = req.header('Authorization'); - const user = await User.findOne({ accessToken }); - if (!user) { - return res.status(401).json({ error: 'Unauthorized' }); - } else { - // Find the thought - const thought = await HappyThoughts.findById(id); - if (!thought) { - return res.status(404).json({ error: 'Thought not found' }); - } - - // OPTIONAL: Check if the user owns the thought (requires a user ref in HappyThoughts) - if (thought.user && thought.user.toString() !== user._id.toString()) { - return res.status(403).json({ error: 'Forbidden: Not your thought' }); - } - - const { message } = req.body; - if (!message) { - return res.status(400).json({ error: 'Message is required' }); - } - - thought.message = message; - await thought.save(); - - res.json({ message: 'Thought updated', thought }); + const thought = await HappyThoughts.findById(id); + if (!thought) { + return res.status(404).json({ error: 'Thought not found' }); } + + // OPTIONAL: Check if the user owns the thought (requires a user ref in HappyThoughts) + if (thought.userId && thought.userId.toString() !== req.user.id) { + return res.status(403).json({ error: 'Forbidden: Not your thought' }); + } + + const { message } = req.body; + if (!message) { + return res.status(400).json({ error: 'Message is required' }); + } + + thought.message = message; + await thought.save(); + + res.json({ message: 'Thought updated', thought }); } catch (error) { console.error('Error updating thought:', error); res.status(400).json({ error: 'Invalid ID format or update error' }); @@ -323,6 +317,7 @@ app.post('/register', async (req, res) => { await user.save(); + // Return the user's accessToken (not a JWT) res.status(201).json({ success: true, message: 'User created', @@ -331,7 +326,6 @@ app.post('/register', async (req, res) => { }); } catch (error) { if (error.code === 11000) { - // Duplicate key error from MongoDB return res.status(400).json({ success: false, message: 'Username already taken', @@ -349,7 +343,6 @@ app.get('/secrets', authenticateUser, (req, res) => { res.json({ secret: 'this is a secret message.' }); }); -// Start the servers app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); From 8a86a110f8b2e0ac451af4c0835b8b9eec28c2c9 Mon Sep 17 00:00:00 2001 From: oskarnordin Date: Sun, 15 Jun 2025 22:39:24 +0200 Subject: [PATCH 07/15] Update Update --- models/HappyThoughts.js | 33 ++++++++++++++++ server.js | 85 ++++++++++++++++++++++++++++++++--------- 2 files changed, 99 insertions(+), 19 deletions(-) create mode 100644 models/HappyThoughts.js diff --git a/models/HappyThoughts.js b/models/HappyThoughts.js new file mode 100644 index 0000000..a54465e --- /dev/null +++ b/models/HappyThoughts.js @@ -0,0 +1,33 @@ +const mongoose = require('mongoose'); + +const HappyThoughtsSchema = new mongoose.Schema({ + message: { + type: String, + required: true, + minlength: 5, + maxlength: 140, + }, + hearts: { + type: Number, + default: 0, + }, + createdAt: { + type: Date, + default: Date.now, + }, + username: { + type: String, + }, + userId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + }, + likedBy: [ + { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + }, + ], +}); + +module.exports = mongoose.model('HappyThoughts', HappyThoughtsSchema); diff --git a/server.js b/server.js index 1a82e54..f2d2c6b 100644 --- a/server.js +++ b/server.js @@ -17,6 +17,12 @@ const happyThoughtsSchema = new mongoose.Schema({ createdAt: { type: Date, default: Date.now }, username: { type: String, required: true }, userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User' }, + likedBy: [ + { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + }, + ], }); const userSchema = new mongoose.Schema({ @@ -124,19 +130,19 @@ app.get('/thoughts/random', async (req, res) => { } }); -//return all thoughts sorted by likes (most likes on top)(has to be placed before the id route) -app.get('/thoughts/likes', async (req, res) => { - try { - const sortedThoughts = await HappyThoughts.find().sort({ hearts: -1 }); - if (sortedThoughts.length > 0) { - res.json(sortedThoughts); - } else { - res.status(404).json({ error: 'No thoughts' }); - } - } catch (error) { - res.status(400).json({ error: 'Invalid request' }); - } -}); +// //return all thoughts sorted by likes (most likes on top)(has to be placed before the id route) +// app.get('/thoughts/likes', async (req, res) => { +// try { +// const sortedThoughts = await HappyThoughts.find().sort({ hearts: -1 }); +// if (sortedThoughts.length > 0) { +// res.json(sortedThoughts); +// } else { +// res.status(404).json({ error: 'No thoughts' }); +// } +// } catch (error) { +// res.status(400).json({ error: 'Invalid request' }); +// } +// }); // return a specific thought by id app.get('/thoughts/:id', async (req, res) => { @@ -226,8 +232,9 @@ app.delete('/thoughts/:id', authenticateUser, async (req, res) => { } }); -app.put('/thoughts/:id/', authenticateUser, async (req, res) => { +app.put('/thoughts/:id', authenticateUser, async (req, res) => { const { id } = req.params; + const { message } = req.body; try { const thought = await HappyThoughts.findById(id); @@ -235,14 +242,18 @@ app.put('/thoughts/:id/', authenticateUser, async (req, res) => { return res.status(404).json({ error: 'Thought not found' }); } - // OPTIONAL: Check if the user owns the thought (requires a user ref in HappyThoughts) - if (thought.userId && thought.userId.toString() !== req.user.id) { + // Only allow the owner to edit + if ( + thought.userId && + thought.userId.toString() !== req.user._id.toString() + ) { return res.status(403).json({ error: 'Forbidden: Not your thought' }); } - const { message } = req.body; - if (!message) { - return res.status(400).json({ error: 'Message is required' }); + if (!message || message.length < 5 || message.length > 140) { + return res + .status(400) + .json({ error: 'Message must be 5-140 characters.' }); } thought.message = message; @@ -343,6 +354,42 @@ app.get('/secrets', authenticateUser, (req, res) => { res.json({ secret: 'this is a secret message.' }); }); +app.post('/thoughts/:id/likes', authenticateUser, async (req, res) => { + const { id } = req.params; + const userId = req.user._id; + + try { + const thought = await HappyThoughts.findById(id); + if (!thought) { + return res.status(404).json({ error: 'Thought not found' }); + } + + // Only add if not already liked + if (!thought.likedBy.some((uid) => uid.equals(userId))) { + thought.likedBy.push(userId); + thought.hearts += 1; + await thought.save(); + } + + res.json(thought); + } catch (error) { + console.error('Error liking thought:', error); + res.status(400).json({ error: 'Invalid ID format or like error' }); + } +}); + +app.get('/thoughts/likes', authenticateUser, async (req, res) => { + try { + const likedThoughts = await HappyThoughts.find({ + likedBy: req.user._id, // <- req.user is set by authenticateUser + }); + res.json(likedThoughts); + } catch (error) { + console.error('Error fetching liked thoughts:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); From f7cd75fc5dedb90a337a324c7320c3381393209b Mon Sep 17 00:00:00 2001 From: Sofie Date: Sun, 15 Jun 2025 23:09:22 +0200 Subject: [PATCH 08/15] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 94342e5..86f14af 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ # Project API backend happy api -https://smilezone77.netlify.app/ \ No newline at end of file +https://smilezone78.netlify.app/ From bf410a94ede0c765335c6d8834ca9b9bc1132eca Mon Sep 17 00:00:00 2001 From: oskarnordin Date: Mon, 16 Jun 2025 11:19:53 +0200 Subject: [PATCH 09/15] add .env --- server.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/server.js b/server.js index f2d2c6b..81ea1ff 100644 --- a/server.js +++ b/server.js @@ -94,9 +94,6 @@ app.use(cors()); app.use(express.json()); // Start defining your routes here -app.get('/', (req, res) => { - res.send('Hello Technigo!'); -}); //return all thoughts app.get('/thoughts', async (req, res) => { From eb13d613304446ec9c5aaa3a8dbe01e5f352f4dc Mon Sep 17 00:00:00 2001 From: oskarnordin Date: Mon, 16 Jun 2025 14:18:23 +0200 Subject: [PATCH 10/15] Endpoint showing Endpoint showing --- package.json | 1 + server.js | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/package.json b/package.json index 9ce8e56..69dff57 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "bcrypt-nodejs": "^0.0.3", "cors": "^2.8.5", "express": "^4.17.3", + "express-list-endpoints": "^7.1.1", "jsonwebtoken": "^9.0.2", "jwt-decode": "^4.0.0", "mongoose": "^8.15.1", diff --git a/server.js b/server.js index 81ea1ff..b933f48 100644 --- a/server.js +++ b/server.js @@ -4,6 +4,7 @@ import fs from 'fs'; import mongoose from 'mongoose'; import crypto from 'crypto'; import bcrypt from 'bcrypt-nodejs'; +import listEndpoints from 'express-list-endpoints'; const port = process.env.PORT || 8081; const app = express(); @@ -95,6 +96,10 @@ app.use(express.json()); // Start defining your routes here +app.get('/', (req, res) => { + res.send(listEndpoints(app)); +}); + //return all thoughts app.get('/thoughts', async (req, res) => { try { From 9dc22b525d3357f244eabca122283edcc57c9f30 Mon Sep 17 00:00:00 2001 From: oskarnordin Date: Mon, 16 Jun 2025 14:23:54 +0200 Subject: [PATCH 11/15] Update endpoints get / Update endpoints get / --- server.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server.js b/server.js index b933f48..ba691df 100644 --- a/server.js +++ b/server.js @@ -96,10 +96,6 @@ app.use(express.json()); // Start defining your routes here -app.get('/', (req, res) => { - res.send(listEndpoints(app)); -}); - //return all thoughts app.get('/thoughts', async (req, res) => { try { @@ -392,6 +388,10 @@ app.get('/thoughts/likes', authenticateUser, async (req, res) => { } }); +app.get('/', (req, res) => { + res.send(listEndpoints(app)); +}); + app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); }); From ac321eaecdaa19ce2d3cfc939bf905ca15435d3b Mon Sep 17 00:00:00 2001 From: oskarnordin Date: Mon, 16 Jun 2025 14:26:59 +0200 Subject: [PATCH 12/15] /endpoints /endpoints --- server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.js b/server.js index ba691df..633266c 100644 --- a/server.js +++ b/server.js @@ -388,7 +388,7 @@ app.get('/thoughts/likes', authenticateUser, async (req, res) => { } }); -app.get('/', (req, res) => { +app.get('/endpoints', (req, res) => { res.send(listEndpoints(app)); }); From 8c08e418b8e999d9cd40af83b0c2b1f321eccb0f Mon Sep 17 00:00:00 2001 From: Sofie Date: Mon, 16 Jun 2025 14:32:12 +0200 Subject: [PATCH 13/15] error --- server.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server.js b/server.js index 81ea1ff..da308a9 100644 --- a/server.js +++ b/server.js @@ -5,7 +5,7 @@ import mongoose from 'mongoose'; import crypto from 'crypto'; import bcrypt from 'bcrypt-nodejs'; -const port = process.env.PORT || 8081; +const port = import.meta.env.PORT || 8081; const app = express(); const happythoughtsData = JSON.parse(fs.readFileSync('./data.json', 'utf-8')); @@ -71,11 +71,11 @@ const authenticateUser = async (req, res, next) => { const HappyThoughts = mongoose.model('HappyThoughts', happyThoughtsSchema); // Connect to MongoDB -mongoose.connect(process.env.MONGO_URL || 'mongodb://localhost/happythoughts'); +mongoose.connect(import.meta.env.MONGO_URL || 'mongodb://localhost/happythoughts'); mongoose.Promise = Promise; // RESET_DB logic -if (process.env.RESET_DB) { +if (import.meta.env.RESET_DB) { const seedDatabase = async () => { console.log('Resetting database!'); await HappyThoughts.deleteMany(); From a1c19da3c1208ceb6787291f59f7dc39b393dffd Mon Sep 17 00:00:00 2001 From: oskarnordin Date: Mon, 16 Jun 2025 14:49:46 +0200 Subject: [PATCH 14/15] const port = process.env.PORT || 8081; const port = process.env.PORT || 8081; --- server.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index fc8f0fc..b019742 100644 --- a/server.js +++ b/server.js @@ -6,7 +6,7 @@ import crypto from 'crypto'; import bcrypt from 'bcrypt-nodejs'; import listEndpoints from 'express-list-endpoints'; -const port = import.meta.env.PORT || 8081; +const port = process.env.PORT || 8081; const app = express(); const happythoughtsData = JSON.parse(fs.readFileSync('./data.json', 'utf-8')); @@ -72,7 +72,9 @@ const authenticateUser = async (req, res, next) => { const HappyThoughts = mongoose.model('HappyThoughts', happyThoughtsSchema); // Connect to MongoDB -mongoose.connect(import.meta.env.MONGO_URL || 'mongodb://localhost/happythoughts'); +mongoose.connect( + import.meta.env.MONGO_URL || 'mongodb://localhost/happythoughts' +); mongoose.Promise = Promise; // RESET_DB logic From 0df7350868c25c1364b279e737c48cb953a8f2f1 Mon Sep 17 00:00:00 2001 From: oskarnordin Date: Mon, 16 Jun 2025 14:53:35 +0200 Subject: [PATCH 15/15] Update Update --- server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server.js b/server.js index b019742..a390c1f 100644 --- a/server.js +++ b/server.js @@ -73,12 +73,12 @@ const HappyThoughts = mongoose.model('HappyThoughts', happyThoughtsSchema); // Connect to MongoDB mongoose.connect( - import.meta.env.MONGO_URL || 'mongodb://localhost/happythoughts' + process.env.MONGO_URL || 'mongodb://localhost/happythoughts' ); mongoose.Promise = Promise; // RESET_DB logic -if (import.meta.env.RESET_DB) { +if (process.env.RESET_DB) { const seedDatabase = async () => { console.log('Resetting database!'); await HappyThoughts.deleteMany();