From d47cf150d60b43acf2299eb59fa6970f201c1f9b Mon Sep 17 00:00:00 2001 From: VariaSlu Date: Thu, 12 Jun 2025 11:05:49 +0200 Subject: [PATCH 01/18] server and thoughts.jsonendpoints --- package.json | 15 +++++++++++++-- thoughts.json | 14 ++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 thoughts.json diff --git a/package.json b/package.json index bf25bb6..43f5a72 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,18 @@ "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", "cors": "^2.8.5", - "express": "^4.17.3", + "express": "^4.21.2", + "express-list-endpoints": "^7.1.1", "nodemon": "^3.0.1" - } + }, + "main": "server.js", + "repository": { + "type": "git", + "url": "git+https://github.com/VariaSlu/js-project-api.git" + }, + "keywords": [], + "bugs": { + "url": "https://github.com/VariaSlu/js-project-api/issues" + }, + "homepage": "https://github.com/VariaSlu/js-project-api#readme" } diff --git a/thoughts.json b/thoughts.json new file mode 100644 index 0000000..c839f9d --- /dev/null +++ b/thoughts.json @@ -0,0 +1,14 @@ +[ + { + "id": 1, + "message": "I'm happy because I'm learning backend!", + "hearts": 5, + "createdAt": "2025-06-05T09:00:00.000Z" + }, + { + "id": 2, + "message": "Coffee makes everything better.", + "hearts": 2, + "createdAt": "2025-06-05T10:00:00.000Z" + } +] \ No newline at end of file From ae945c0e4ffc1334c90d22dfce1f1f354604fc5f Mon Sep 17 00:00:00 2001 From: VariaSlu Date: Mon, 23 Jun 2025 22:04:21 +0200 Subject: [PATCH 02/18] seetup mongodb --- package.json | 2 ++ server.js | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 43f5a72..f937584 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,10 @@ "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", "cors": "^2.8.5", + "dotenv": "^16.5.0", "express": "^4.21.2", "express-list-endpoints": "^7.1.1", + "mongoose": "^8.16.0", "nodemon": "^3.0.1" }, "main": "server.js", diff --git a/server.js b/server.js index f47771b..7cd600d 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,16 @@ import cors from "cors" import express from "express" +import listEndpoints from "express-list-endpoints"; +//import thoughtsData from "./thoughts.json" assert { type: 'json' }; + +import mongoose from 'mongoose'; +import dotenv from 'dotenv'; + +dotenv.config(); + +mongoose.connect(process.env.MONGO_URL) + .then(() => console.log('Connected to MongoDB!')) + .catch((err) => console.error('DB error:', err)); // 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: @@ -11,11 +22,32 @@ const app = express() app.use(cors()) app.use(express.json()) +//set up what we need for data base from Jennies video +const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/something" +mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true }) +mongoose.Promise = Promise + // Start defining your routes here app.get("/", (req, res) => { - res.send("Hello Technigo!") + res.json({ + message: 'Welcome to the Happy Thoughts API!', + endpoints: listEndpoints(app) + }) }) +app.get('/thoughts', (req, res) => { + res.json(thoughtsData); +}); + +app.get('/thoughts/:id', (req, res) => { + const thought = thoughtsData.find(t => t.id === +req.params.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}`) From f2be46aa283e279190b2785a1c24ddf796ac047f Mon Sep 17 00:00:00 2001 From: VariaSlu Date: Wed, 6 Aug 2025 10:21:08 +0200 Subject: [PATCH 03/18] set up MongoDB server, created Thouhgtt model and GET/POST/thoughts routes --- models/Thought.js | 18 +++++++++++++ package.json | 3 ++- server.js | 65 ++++++++++++++++++++++++++++------------------- 3 files changed, 59 insertions(+), 27 deletions(-) create mode 100644 models/Thought.js diff --git a/models/Thought.js b/models/Thought.js new file mode 100644 index 0000000..a42f9d3 --- /dev/null +++ b/models/Thought.js @@ -0,0 +1,18 @@ +import mongoose from 'mongoose'; + +export const Thought = mongoose.model('Thought', { + message: { + type: String, + required: true, + minlength: 5, + maxlength: 140, + }, + heats: { + type: Number, + default: 0 + }, + createdAt: { + type: Date, + default: () => new Date(), + } +}) \ No newline at end of file diff --git a/package.json b/package.json index f937584..b9362e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "project-api", "version": "1.0.0", + "type": "module", "description": "Project API", "scripts": { "start": "babel-node server.js", @@ -29,4 +30,4 @@ "url": "https://github.com/VariaSlu/js-project-api/issues" }, "homepage": "https://github.com/VariaSlu/js-project-api#readme" -} +} \ No newline at end of file diff --git a/server.js b/server.js index 7cd600d..d4f9677 100644 --- a/server.js +++ b/server.js @@ -1,54 +1,67 @@ import cors from "cors" import express from "express" -import listEndpoints from "express-list-endpoints"; -//import thoughtsData from "./thoughts.json" assert { type: 'json' }; - import mongoose from 'mongoose'; import dotenv from 'dotenv'; +import { Thought } from "./models/Thought.js" +import expressListEndpoints from "express-list-endpoints"; dotenv.config(); -mongoose.connect(process.env.MONGO_URL) - .then(() => console.log('Connected to MongoDB!')) - .catch((err) => console.error('DB error:', err)); - // 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 + +// middlewares to enable cors and json body parsing app.use(cors()) app.use(express.json()) -//set up what we need for data base from Jennies video -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/something" -mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true }) -mongoose.Promise = Promise +// Connect to MongoDB +mongoose + .connect(process.env.MONGO_URL) + .then(() => { + console.log('Connected to MongoDB!'); + seed(); // optional: seed data only if DB is empty + }) + .catch((err) => { + console.error('Mongo error', err); + }); -// Start defining your routes here +// Optional: seed one thought if DB is empty +const seed = async () => { + const existing = await Thought.find(); + if (existing.length === 0) { + await new Thought({ + message: 'This is my very first seeded thought!', + }).save(); + console.log('Seeded one thought'); + } +}; + +//basee route app.get("/", (req, res) => { res.json({ - message: 'Welcome to the Happy Thoughts API!', - endpoints: listEndpoints(app) + message: "Welcome to Happy Thougts API", + endpoints: listEndpoints(app), }) }) -app.get('/thoughts', (req, res) => { - res.json(thoughtsData); -}); - -app.get('/thoughts/:id', (req, res) => { - const thought = thoughtsData.find(t => t.id === +req.params.id); - if (!thought) { - res.status(404).json({ error: 'Thought not found' }); - } else { - res.json(thought); +// Get all thoughts +app.get('/thoughts', async (req, res) => { + try { + const thoughts = await Thought.find() + .sort({ createdAt: -1 }) + .limit(20); + res.json(thoughts); + } catch (err) { + res.status(500).json({ error: 'Could not fetch thoughts' }); } }); -// Start the server + +// Start server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`) }) From e58ff193336f65855cf8479390cb323eb8fd4fd6 Mon Sep 17 00:00:00 2001 From: VariaSlu Date: Fri, 8 Aug 2025 10:51:50 +0200 Subject: [PATCH 04/18] addd POST and thoughts rout --- models/Thought.js | 2 +- server.js | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/models/Thought.js b/models/Thought.js index a42f9d3..a22b6a5 100644 --- a/models/Thought.js +++ b/models/Thought.js @@ -7,7 +7,7 @@ export const Thought = mongoose.model('Thought', { minlength: 5, maxlength: 140, }, - heats: { + hearts: { type: Number, default: 0 }, diff --git a/server.js b/server.js index d4f9677..906a6d5 100644 --- a/server.js +++ b/server.js @@ -23,7 +23,7 @@ mongoose .connect(process.env.MONGO_URL) .then(() => { console.log('Connected to MongoDB!'); - seed(); // optional: seed data only if DB is empty + seed(); // optional }) .catch((err) => { console.error('Mongo error', err); @@ -40,6 +40,7 @@ const seed = async () => { } }; + //basee route app.get("/", (req, res) => { res.json({ @@ -60,6 +61,43 @@ app.get('/thoughts', async (req, res) => { } }); +app.post('/thoughts/:id/like', async (req, res) => { + const { id } = req.params; + + try { + console.log('ID received:', id); //for debugginng + + const updatedThought = await Thought.findByIdAndUpdate( + id, + { $inc: { hearts: 1 } }, + { new: true } //return the updaated document + ); + + if (!updatedThought) { + return res.status(404).json({ error: 'Thought not found' }); + } + + res.json(updatedThought); + + } catch (err) { + res.status(400).json({ + error: 'Invalid ID or request', + details: err.message //show whats wrong + }); + } + +}); + +app.post('/thoughts', async (req, res) => { + const { message } = req.body; + + try { + const newThought = await new Thought({ message }).save(); + res.status(201).json(newThought); + } catch (err) { + res.status(400).json({ error: 'Could not save thought', details: err.message }); + } +}); // Start server app.listen(port, () => { From 5681c0523840fca711c0a9d521aa1451b876f3bb Mon Sep 17 00:00:00 2001 From: VariaSlu Date: Fri, 8 Aug 2025 15:01:53 +0200 Subject: [PATCH 05/18] Add GET /thoughts/:id route and fix listEndpoints import --- server.js | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index 906a6d5..9abe0b8 100644 --- a/server.js +++ b/server.js @@ -3,7 +3,7 @@ import express from "express" import mongoose from 'mongoose'; import dotenv from 'dotenv'; import { Thought } from "./models/Thought.js" -import expressListEndpoints from "express-list-endpoints"; +import ListEndpoints from "express-list-endpoints"; dotenv.config(); @@ -35,6 +35,7 @@ const seed = async () => { if (existing.length === 0) { await new Thought({ message: 'This is my very first seeded thought!', + hearts: 0 }).save(); console.log('Seeded one thought'); } @@ -61,6 +62,41 @@ app.get('/thoughts', async (req, res) => { } }); +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', + details: err.message, + }); + } +}); + +app.delete('/thoughts/:id', async (req, res) => { + const { id } = req.params; + + try { + const deletedThought = await Thought.findByIdAndDelete(id); + + if (!deletedThought) { + return res.status(404).json({ error: 'Thought not found' }); + } + + res.status(200).json({ success: true, message: 'Thought deleted' }); + } catch (err) { + res.status(400).json({ error: 'Invalid ID', details: err.message }); + } +}); + app.post('/thoughts/:id/like', async (req, res) => { const { id } = req.params; From 703c22816e60b016058a524e7c1f3726cf225d5b Mon Sep 17 00:00:00 2001 From: VariaSlu Date: Fri, 8 Aug 2025 15:25:58 +0200 Subject: [PATCH 06/18] Added input validation for thoughts: required and min lengt --- server.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/server.js b/server.js index 9abe0b8..c145273 100644 --- a/server.js +++ b/server.js @@ -131,10 +131,22 @@ app.post('/thoughts', async (req, res) => { const newThought = await new Thought({ message }).save(); res.status(201).json(newThought); } catch (err) { - res.status(400).json({ error: 'Could not save thought', details: err.message }); + // Check for validation error + if (err.name === 'ValidationError') { + return res.status(400).json({ + error: 'Validation failed', + details: err.errors.message?.message || 'Invalid input' + }); + } + + res.status(500).json({ + error: 'Could not save thought', + details: err.message, + }); } }); + // Start server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`) From 17e689082df653a13b9f3e8346b2b78fed97f20e Mon Sep 17 00:00:00 2001 From: VariaSlu Date: Sat, 9 Aug 2025 18:26:25 +0200 Subject: [PATCH 07/18] user authentication with signup and login routes, password hashing, and validation --- models/User.js | 38 ++++++++++++++++++++++++++++++++++++++ package.json | 4 +++- server.js | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 models/User.js diff --git a/models/User.js b/models/User.js new file mode 100644 index 0000000..22f6a82 --- /dev/null +++ b/models/User.js @@ -0,0 +1,38 @@ +import mongoose from 'mongoose'; +import bcrypt from 'bcryptjs'; + + +const userSchema = new mongoose.Schema( + { + email: { + type: String, + required: true, + trim: true, + lowercase: true, + unique: true, + match: [/^\S+@\S+\.\S+$/, 'Invalid email format'] + }, + password: { + type: String, + required: true, + minlength: 6 + }, + + }, + { timestamps: true } // <-- options go here, not inside the fields +); + +// Hash password before saving +userSchema.pre('save', async function (next) { + if (!this.isModified('password')) return next(); // Skip if password not changed + this.password = await bcrypt.hash(this.password, 10); // Salt rounds = 10 + next(); +}); + +// Method to compare passwords later +userSchema.methods.comparePassword = function (candidatePassword) { + return bcrypt.compare(candidatePassword, this.password); +}; + + +export const User = mongoose.model('User', userSchema); diff --git a/package.json b/package.json index b9362e4..3c14875 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,12 @@ "@babel/core": "^7.17.9", "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", + "bcryptjs": "^3.0.2", "cors": "^2.8.5", "dotenv": "^16.5.0", "express": "^4.21.2", "express-list-endpoints": "^7.1.1", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.16.0", "nodemon": "^3.0.1" }, @@ -30,4 +32,4 @@ "url": "https://github.com/VariaSlu/js-project-api/issues" }, "homepage": "https://github.com/VariaSlu/js-project-api#readme" -} \ No newline at end of file +} diff --git a/server.js b/server.js index c145273..3114319 100644 --- a/server.js +++ b/server.js @@ -4,6 +4,8 @@ import mongoose from 'mongoose'; import dotenv from 'dotenv'; import { Thought } from "./models/Thought.js" import ListEndpoints from "express-list-endpoints"; +import { User } from './models/User.js'; +import bcrypt from 'bcryptjs'; dotenv.config(); @@ -146,6 +148,50 @@ app.post('/thoughts', async (req, res) => { } }); +app.post('/signup', async (req, res) => { + const { email, password } = req.body; + + try { + const newUser = await new User({ email, password }).save(); + res.status(201).json({ + email: newUser.email, + _id: newUser._id + }); + } catch (err) { + res.status(400).json({ error: 'Could not create user', details: err.message }); + } +}); + +// LOGIN ROUTE +app.post('/login', async (req, res) => { + const { email, password } = req.body; + + try { + // 1. Check if user exists + const user = await User.findOne({ email }); + if (!user) { + return res.status(401).json({ error: 'Invalid email or password' }); + } + + // 2. Compare passwords + const isMatch = await user.comparePassword(password); + if (!isMatch) { + return res.status(401).json({ error: 'Invalid email or password' }); + } + + // 3. Login successful + res.json({ + success: true, + message: 'Login successful', + userId: user._id, + email: user.email + }); + + } catch (err) { + res.status(500).json({ error: 'Server error', details: err.message }); + } +}); + // Start server app.listen(port, () => { From e4d7314850482b2fc6cf4116df5eca81cc31394f Mon Sep 17 00:00:00 2001 From: VariaSlu Date: Sun, 10 Aug 2025 22:03:05 +0200 Subject: [PATCH 08/18] nnow have X-User-Id header support for creating thoughs --- models/Thought.js | 5 +++++ server.js | 21 ++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/models/Thought.js b/models/Thought.js index a22b6a5..cac895c 100644 --- a/models/Thought.js +++ b/models/Thought.js @@ -14,5 +14,10 @@ export const Thought = mongoose.model('Thought', { createdAt: { type: Date, default: () => new Date(), + }, + createdBy: { + type: mongoose.Schema.Types.ObjectId, + ref: 'User', + required: false } }) \ No newline at end of file diff --git a/server.js b/server.js index 3114319..d40b23f 100644 --- a/server.js +++ b/server.js @@ -128,9 +128,28 @@ app.post('/thoughts/:id/like', async (req, res) => { app.post('/thoughts', async (req, res) => { const { message } = req.body; + const userId = req.header('x-user-id'); + console.log('Headers received:', req.headers); //fo test try { - const newThought = await new Thought({ message }).save(); + if (!userId) { + return res.status(401).json({ error: 'Missing X-User-Id header' }) + } + + const user = await User.findById(userId); + if (!user) { + return res.status(401).json({ error: 'Invalid user' }); + } + + + const newThought = await new Thought({ + message, + ceratedBy: user._id + }).save(); + + // (Optional) populate the user info + // await newThought.populate('createdBy', 'email username'); + res.status(201).json(newThought); } catch (err) { // Check for validation error From 517689f681ae3f3f87c13efaa27d56bb7b6a9b4b Mon Sep 17 00:00:00 2001 From: VariaSlu Date: Mon, 11 Aug 2025 20:50:52 +0200 Subject: [PATCH 09/18] linked thoughts to useers --- models/Thought.js | 2 +- server.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/models/Thought.js b/models/Thought.js index cac895c..6e27566 100644 --- a/models/Thought.js +++ b/models/Thought.js @@ -18,6 +18,6 @@ export const Thought = mongoose.model('Thought', { createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User', - required: false + required: true } }) \ No newline at end of file diff --git a/server.js b/server.js index d40b23f..971302d 100644 --- a/server.js +++ b/server.js @@ -128,7 +128,7 @@ app.post('/thoughts/:id/like', async (req, res) => { app.post('/thoughts', async (req, res) => { const { message } = req.body; - const userId = req.header('x-user-id'); + const userId = req.header('x-user-id'); // temporary auth console.log('Headers received:', req.headers); //fo test try { @@ -144,7 +144,7 @@ app.post('/thoughts', async (req, res) => { const newThought = await new Thought({ message, - ceratedBy: user._id + createdBy: user._id, }).save(); // (Optional) populate the user info @@ -156,13 +156,13 @@ app.post('/thoughts', async (req, res) => { if (err.name === 'ValidationError') { return res.status(400).json({ error: 'Validation failed', - details: err.errors.message?.message || 'Invalid input' + details: err.message }); } res.status(500).json({ error: 'Could not save thought', - details: err.message, + details: err.message }); } }); From d775205bcda2bbbe4ea4c3016fc8343bf3c16cde Mon Sep 17 00:00:00 2001 From: VariaSlu Date: Tue, 12 Aug 2025 13:11:32 +0200 Subject: [PATCH 10/18] documentation at / using express-list-endpoints --- server.js | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/server.js b/server.js index 971302d..b960806 100644 --- a/server.js +++ b/server.js @@ -6,6 +6,8 @@ import { Thought } from "./models/Thought.js" import ListEndpoints from "express-list-endpoints"; import { User } from './models/User.js'; import bcrypt from 'bcryptjs'; +import listEndpoints from 'express-list-endpoints'; + dotenv.config(); @@ -45,19 +47,30 @@ const seed = async () => { //basee route -app.get("/", (req, res) => { - res.json({ - message: "Welcome to Happy Thougts API", - endpoints: listEndpoints(app), - }) -}) +app.get('/', (req, res) => { + try { + const endpoints = listEndpoints(app).map(e => ({ + path: e.path, + methods: e.methods.sort(), + })); + res.json({ + name: 'Happy Thoughts API', + version: '1.0.0', + endpoints + }); + } catch (err) { + res.status(500).json({ error: 'Failed to list endpoints', details: err.message }); + } +}); // Get all thoughts app.get('/thoughts', async (req, res) => { try { const thoughts = await Thought.find() .sort({ createdAt: -1 }) - .limit(20); + .limit(20) + .populate('createdBy', 'email usename'); + res.json(thoughts); } catch (err) { res.status(500).json({ error: 'Could not fetch thoughts' }); From 548f9925e566c09a4a5552ed7727084f3d03015c Mon Sep 17 00:00:00 2001 From: VariaSlu Date: Tue, 12 Aug 2025 21:03:39 +0200 Subject: [PATCH 11/18] JWT auth and protect POST /thoughts; verified token flow --- server.js | 119 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 72 insertions(+), 47 deletions(-) diff --git a/server.js b/server.js index b960806..9e3e876 100644 --- a/server.js +++ b/server.js @@ -3,10 +3,9 @@ import express from "express" import mongoose from 'mongoose'; import dotenv from 'dotenv'; import { Thought } from "./models/Thought.js" -import ListEndpoints from "express-list-endpoints"; import { User } from './models/User.js'; -import bcrypt from 'bcryptjs'; import listEndpoints from 'express-list-endpoints'; +import jwt from 'jsonwebtoken'; dotenv.config(); @@ -27,7 +26,7 @@ mongoose .connect(process.env.MONGO_URL) .then(() => { console.log('Connected to MongoDB!'); - seed(); // optional + //seed(); // optional }) .catch((err) => { console.error('Mongo error', err); @@ -45,6 +44,26 @@ const seed = async () => { } }; +const auth = (req, res, next) => { + const raw = req.header('Authorization') || ''; + const token = raw.startsWith('Bearer ') ? raw.slice(7) : null; + + console.log('[AUTH] raw header =', JSON.stringify(raw)); + console.log('[AUTH] token len =', token ? token.length : 0); + console.log('[AUTH] token parts =', token ? token.split('.').length : 0); + + if (!token) return res.status(401).json({ error: 'Missing token' }); + + try { + const payload = jwt.verify(token, process.env.JWT_SECRET); + req.userId = payload.userId; + return next(); + } catch (err) { + console.error('JWT verify error:', err.message); + return res.status(401).json({ error: 'Invalid or expired token', details: err.message }); + } +}; + //basee route app.get('/', (req, res) => { @@ -69,7 +88,7 @@ app.get('/thoughts', async (req, res) => { const thoughts = await Thought.find() .sort({ createdAt: -1 }) .limit(20) - .populate('createdBy', 'email usename'); + .populate('createdBy', 'email'); res.json(thoughts); } catch (err) { @@ -96,21 +115,6 @@ app.get('/thoughts/:id', async (req, res) => { } }); -app.delete('/thoughts/:id', async (req, res) => { - const { id } = req.params; - - try { - const deletedThought = await Thought.findByIdAndDelete(id); - - if (!deletedThought) { - return res.status(404).json({ error: 'Thought not found' }); - } - - res.status(200).json({ success: true, message: 'Thought deleted' }); - } catch (err) { - res.status(400).json({ error: 'Invalid ID', details: err.message }); - } -}); app.post('/thoughts/:id/like', async (req, res) => { const { id } = req.params; @@ -139,44 +143,57 @@ app.post('/thoughts/:id/like', async (req, res) => { }); -app.post('/thoughts', async (req, res) => { +app.post('/thoughts', auth, async (req, res) => { const { message } = req.body; - const userId = req.header('x-user-id'); // temporary auth - console.log('Headers received:', req.headers); //fo test try { - if (!userId) { - return res.status(401).json({ error: 'Missing X-User-Id header' }) - } - - const user = await User.findById(userId); - if (!user) { - return res.status(401).json({ error: 'Invalid user' }); - } - - const newThought = await new Thought({ message, - createdBy: user._id, + createdBy: req.userId }).save(); - // (Optional) populate the user info - // await newThought.populate('createdBy', 'email username'); - res.status(201).json(newThought); } catch (err) { - // Check for validation error if (err.name === 'ValidationError') { - return res.status(400).json({ - error: 'Validation failed', - details: err.message - }); + return res.status(400).json({ error: 'Validation failed', details: err.message }); } + res.status(500).json({ error: 'Could not save thought', details: err.message }); + } +}); - res.status(500).json({ - error: 'Could not save thought', - details: err.message - }); +app.patch('/thoughts/:id', auth, async (req, res) => { + const { message } = req.body; + if (typeof message !== 'string' || message.length < 5 || message.length > 140) { + return res.status(400).json({ error: 'Message must be 5–140 chars' }); + } + + try { + const t = await Thought.findById(req.params.id); + if (!t) return res.status(404).json({ error: 'Thought not found' }); + if (String(t.createdBy) !== req.userId) { + return res.status(403).json({ error: 'Not your thought' }); + } + + t.message = message; + await t.save(); + res.json(t); + } catch (err) { + res.status(400).json({ error: 'Invalid ID', details: err.message }); + } +}); + +app.delete('/thoughts/:id', auth, async (req, res) => { + try { + const t = await Thought.findById(req.params.id); + if (!t) return res.status(404).json({ error: 'Thought not found' }); + if (String(t.createdBy) !== req.userId) { + return res.status(403).json({ error: 'Not your thought' }); + } + + await t.deleteOne(); + res.json({ success: true, message: 'Thought deleted' }); + } catch (err) { + res.status(400).json({ error: 'Invalid ID', details: err.message }); } }); @@ -211,14 +228,22 @@ app.post('/login', async (req, res) => { return res.status(401).json({ error: 'Invalid email or password' }); } - // 3. Login successful + // after password check passes + const accessToken = jwt.sign( + { userId: user._id }, + process.env.JWT_SECRET, + { expiresIn: '7d' } + ); + res.json({ success: true, message: 'Login successful', userId: user._id, - email: user.email + email: user.email, + accessToken }); + } catch (err) { res.status(500).json({ error: 'Server error', details: err.message }); } From 750bb6b6c988a0c2f72f16ca89111745df0832b0 Mon Sep 17 00:00:00 2001 From: VariaSlu Date: Wed, 13 Aug 2025 11:30:25 +0200 Subject: [PATCH 12/18] Deploy-ready updated readmee --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 3c14875..5916d27 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "express": "^4.21.2", "express-list-endpoints": "^7.1.1", "jsonwebtoken": "^9.0.2", + "mongodb": "^6.18.0", "mongoose": "^8.16.0", "nodemon": "^3.0.1" }, From 62f3decfa4bede6c00c87537224cde7026d0da33 Mon Sep 17 00:00:00 2001 From: VariaSlu Date: Wed, 13 Aug 2025 11:32:15 +0200 Subject: [PATCH 13/18] readmeee update --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 0f9f073..98d9b1b 100644 --- a/README.md +++ b/README.md @@ -9,3 +9,27 @@ Install dependencies with `npm install`, then start the server by running `npm r ## 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. + +# Happy Thoughts API + +## Run locally +npm install +node server.js +# .env required: +MONGO_URL=... +JWT_SECRET=... + +## Endpoints +GET / -> API docs +GET /thoughts -> list latest 20 +GET /thoughts/:id -> single thought +POST /thoughts/:id/like -> like +POST /signup -> create user +POST /login -> login (returns accessToken) +POST /thoughts -> create (JWT required) +PATCH /thoughts/:id -> update (JWT + author-only) +DELETE /thoughts/:id -> delete (JWT + author-only) + +## Deploy +Deployed on Render: +https://js-project-api-862g.onrender.com/ From 45c393c60b3ea408aa4bdcda05db74110ac18350 Mon Sep 17 00:00:00 2001 From: VariaSlu Date: Fri, 15 Aug 2025 17:25:34 +0200 Subject: [PATCH 14/18] readme mongo url added --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 98d9b1b..48878dc 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ Every project should be deployed somewhere. Be sure to include the link to the d npm install node server.js # .env required: -MONGO_URL=... +MONGO_URL=mongodb+srv://varvaraslugina:1zqKjsfqPiWDBjkU@cluster0.cttoete.mongodb.net/test?retryWrites=true&w=majority&appName=Cluster0 + JWT_SECRET=... ## Endpoints From 3c9e2808fb73a8bcd732c0375fc6ff9339eb16de Mon Sep 17 00:00:00 2001 From: VariaSlu Date: Fri, 15 Aug 2025 17:32:03 +0200 Subject: [PATCH 15/18] deleted mongo url from read me --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 48878dc..4c5c468 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Every project should be deployed somewhere. Be sure to include the link to the d npm install node server.js # .env required: -MONGO_URL=mongodb+srv://varvaraslugina:1zqKjsfqPiWDBjkU@cluster0.cttoete.mongodb.net/test?retryWrites=true&w=majority&appName=Cluster0 +MONGO_URL= JWT_SECRET=... From 39bc956b1fac29c18447085363ecfa10e15319d7 Mon Sep 17 00:00:00 2001 From: VariaSlu Date: Sun, 17 Aug 2025 13:00:08 +0200 Subject: [PATCH 16/18] readme updated --- README.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/README.md b/README.md index 4c5c468..f4d97c9 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,7 @@ Install dependencies with `npm install`, then start the server by running `npm r ## 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. - -# Happy Thoughts API - -## Run locally -npm install -node server.js -# .env required: -MONGO_URL= - -JWT_SECRET=... +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. https://happyhappyhappyhappy.netlify.app/ ## Endpoints GET / -> API docs From 5dcb97830e61f235c112e1c1a4379f4ed85c6c5d Mon Sep 17 00:00:00 2001 From: VariaSlu Date: Sun, 17 Aug 2025 13:03:46 +0200 Subject: [PATCH 17/18] clean --- server.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/server.js b/server.js index 9e3e876..3793141 100644 --- a/server.js +++ b/server.js @@ -32,7 +32,7 @@ mongoose console.error('Mongo error', err); }); -// Optional: seed one thought if DB is empty +// seed one thought if DB is empty (optional) const seed = async () => { const existing = await Thought.find(); if (existing.length === 0) { @@ -120,7 +120,6 @@ app.post('/thoughts/:id/like', async (req, res) => { const { id } = req.params; try { - console.log('ID received:', id); //for debugginng const updatedThought = await Thought.findByIdAndUpdate( id, From 02d65406d46326fddbefa103c92b167f0321fdff Mon Sep 17 00:00:00 2001 From: VariaSlu Date: Sun, 17 Aug 2025 13:07:19 +0200 Subject: [PATCH 18/18] more clean --- models/User.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/User.js b/models/User.js index 22f6a82..9cc4d05 100644 --- a/models/User.js +++ b/models/User.js @@ -29,7 +29,7 @@ userSchema.pre('save', async function (next) { next(); }); -// Method to compare passwords later +// to compare passwords later userSchema.methods.comparePassword = function (candidatePassword) { return bcrypt.compare(candidatePassword, this.password); };