Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0d0657e
set up endpoints and created three routes: endpoints documentation, a…
Jan 20, 2026
def493d
made file structure for models,routes and middleware. added routes fo…
Apr 23, 2026
dc6f1fd
added js to authMiddleware for render debugging
Apr 27, 2026
3689eb9
more debugging for render
Apr 27, 2026
eee75ce
changed the happy-thoughts route names
Apr 27, 2026
0f7144b
added updated some of the happy thoughts routes with the new mongoose…
Apr 27, 2026
8ffa85f
added seeding database for happy thoughts testing
Apr 27, 2026
b095cc2
debugged id route: added async await and changed id param
May 20, 2026
788f053
added seeding users to userRoutes
May 20, 2026
5f1cf93
variable change in seeding users
May 20, 2026
5fe76ba
name debug for frontend
May 22, 2026
8a840fa
updated user model with first and last name
May 27, 2026
d85eec1
added first and lastname to signup route and added a get route for a …
May 27, 2026
a41c7e5
updated signup route to send accessToken
May 27, 2026
57f33da
debugging signup route
May 27, 2026
3aed50a
more debugging for signup route
May 27, 2026
d24e384
fixed variable name
May 27, 2026
1f47018
debugg in auth middleware
May 27, 2026
62fc64e
happy thoughts route: added sort by desc to get route
May 27, 2026
93dfe64
added reset users
May 27, 2026
b0adbbe
added email validation to signup and login route and added patch rout…
May 29, 2026
c7bd4ae
added ping route for cron-job, to wake up api
May 29, 2026
64d89eb
added auth middleware to id patch route
May 29, 2026
9fc67b7
commented out seeding thoughts
May 29, 2026
8e5cbd0
deleted seeding datbased
May 29, 2026
b3e90c4
added render link in readme
May 29, 2026
2048618
refactor: enhance authentication and author validation in HappyThough…
Jun 24, 2026
323cea9
refactor: improve author population in HappyThought routes
Jun 24, 2026
a39640d
refactor: enhance author validation in delete route for HappyThoughts
Jun 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# Project API

render: https://happy-thoughts-api-o47r.onrender.com

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
Expand Down
7 changes: 0 additions & 7 deletions data.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,5 @@
"hearts": 3,
"createdAt": "2025-05-20T03:57:40.322Z",
"__v": 0
},
{
"_id": "682bab8c12155b00101732ce",
"message": "Berlin baby",
"hearts": 37,
"createdAt": "2025-05-19T22:07:08.999Z",
"__v": 0
}
]
33 changes: 33 additions & 0 deletions middleware/authMiddleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { User } from "../models/User.js"

export const authenticateUser = async (request, response, next) => {
try {

const authHeader = request.header("Authorization") || request.get("Authorization")

if(!authHeader) {
return response.status(401).json({
message: "Authentication missing or invalid",
loggedOut: true
})
}

const user = await User.findOne({ accessToken: authHeader.replace("Bearer ", "")})

if (user) {
request.user = user
next()
} else {
response.status(401).json({
message: "Authentication missing or invalid",
loggedOut: true
})
}

} catch (error) {
response.status(500).json({
message: "internal server error",
error: error.message
})
}
}
22 changes: 22 additions & 0 deletions models/HappyThought.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import mongoose from "mongoose"

const happyThoughtSchema = new mongoose.Schema({
message: {
type: String,
required: true
},
hearts: {
type: Number
},
createdAt: {
type: Date,
default: Date.now
},
author: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true
}
})

export const HappyThought = mongoose.model("HappyThought", happyThoughtSchema)
28 changes: 28 additions & 0 deletions models/User.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import crypto from "crypto"
import mongoose from "mongoose"

const userSchema = new mongoose.Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
accessToken: {
type: String,
default: () => crypto.randomBytes(128).toString("hex")
}
})

export const User = mongoose.model("User", userSchema)
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "project-api",
"version": "1.0.0",
"description": "Project API",
"type": "module",
"scripts": {
"start": "babel-node server.js",
"dev": "nodemon server.js --exec babel-node"
Expand All @@ -12,8 +13,13 @@
"@babel/core": "^7.17.9",
"@babel/node": "^7.16.8",
"@babel/preset-env": "^7.16.11",
"cors": "^2.8.5",
"express": "^4.17.3",
"bcrypt": "^6.0.0",
"cors": "^2.8.6",
"dotenv": "^17.2.3",
"express": "^4.22.1",
"express-list-endpoints": "^7.1.1",
"mongodb": "^7.2.0",
"mongoose": "^9.5.0",
"nodemon": "^3.0.1"
}
}
247 changes: 247 additions & 0 deletions routes/happyThoughtsRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
import express from "express"
import mongoose from "mongoose"
import { authenticateUser } from "../middleware/authMiddleware.js"
import { HappyThought } from "../models/HappyThought.js"

const router = express.Router()

router.get("/", async (request, response) => {

const { minLikes } = request.query

const query = {}

if (minLikes) {
query.hearts = {$gte: Number(minLikes)}
}

try {

const filteredMessages = await HappyThought.find(query)
.sort({ createdAt: "desc" })
.populate("author", "firstName lastName")

if (filteredMessages.length === 0) {
return response.status(404).json({
success: false,
response: [],
message: "No thoughts were found for that query"
})
}

return response.status(200).json({
success: true,
response: filteredMessages,
message: "Success"
})
} catch (error) {
return response.status(500).json({
success: false,
response: [],
message: error
})
}
})

router.post("/", authenticateUser, async (request, response) => {
const { message } = request.body

try {
const newHappyThought = await new HappyThought({ message, author: request.user._id }).save()
await newHappyThought.populate("author", "firstName lastName")
response.status(201).json({
success: true,
response: newHappyThought,
message: "Happy thought created successfully"
})
} catch (error) {
response.status(500).json({
success: false,
response: error,
message: "Couldn't create happy thought"
})
}
})


router.get("/:id", async (request, response) => {
const { id } = request.params

try {
const happyThought = await HappyThought.findById(id).populate("author", "firstName lastName")
if (!happyThought) {
return response.status(404).json({
success: false,
response: null,
message: "Happy thought not found"
})
}

response.status(200).json({
success: true,
response: happyThought
})
} catch (error) {
response.status(500).json({
success: false,
response: error,
message: "Happy thought couldn't be found"
})
}

})


router.delete("/:id", authenticateUser, async (request, response) => {
const { id } = request.params

if (!id || !mongoose.isValidObjectId(id)) {
return response.status(400).json({
success: false,
response: null,
message: "Invalid ID"
})
}

if (!request.user || !request.user._id) {
return response.status(401).json({
success: false,
response: null,
message: "User not authenticated"
})
}

try {

const thought = await HappyThought.findById(id)

if (!thought) {
return response.status(404).json({
success: false,
response: null,
message: "Happy thought not found"
})
}

// säker ägarcheck: fungerar om author är ObjectId eller populated object
const authorId = thought.author?._id ? thought.author._id.toString() : thought.author?.toString()
if (!authorId || authorId !== request.user._id.toString()) {
return response.status(403).json({
success: false,
response: null,
message: "You are not authorized to delete this thought"
})
}

const deletedThought = await HappyThought.findByIdAndDelete(id)

if (!deletedThought) {
return response.status(404).json({
success: false,
response: null,
message: "Happy thought not found"
})
}

response.status(200).json({
success: true,
response: deletedThought,
message: "Happy thought deleted successfully"
})
} catch (error) {
response.status(500).json({
success: false,
response: error,
message: "Error deleting happy thought"
})
}
})

router.patch("/:id", authenticateUser, async (request, response) => {
const { id } = request.params
const { message } = request.body

try {

if (!message || message.length <5 || message.length > 140) {
return response.status(400).json({
success: false,
response: null,
message: "Message must be between 5 and 140 characters"
})
}

const thought = await HappyThought.findById(id)

if (!thought) {
return response.status(404).json({
success: false,
response: null,
message: "Happy Thought not found"
})
}

if (!thought.author.equals(request.user._id)) {
return response.status(403).json({
success: false,
response: null,
message: "You are not authorized to update this thought"
})
}

const updatedHappyThought = await HappyThought.findByIdAndUpdate(id, { message }, { new: true})

if (!updatedHappyThought) {
return response.status(404).json({
success: false,
response: null,
message: "Happy Thought not found"
})
}

return response.status(200).json({
success: true,
response: updatedHappyThought,
message: "Happy Thought updated successfully"
})

} catch (error) {
response.status(500).json({
success: false,
response: null,
message: "Could not update thought"
})
}
})

router.patch("/:id/like", async (request, response) => {
const { id } = request.params

try {
const happyThought = await HappyThought.findByIdAndUpdate(id, { $inc: {hearts: 1}}, { new: true })

if (!happyThought) {
return response.status(404).json({
success: false,
response: null,
message: "Happy thought not found"
})
}

response.status(200).json({
success: true,
response: happyThought,
message: "Happy thought liked successfully"
})
} catch (error) {
response.status(500).json({
success: false,
response: error,
message: "Error liking happy thought"
})
}

})


export default router
Loading