Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ node_modules
.env.development.local
.env.test.local
.env.production.local
package-lock.json
package-lock.json
.dogs-frontend
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ 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.


Deployed: https://lillebrorgrodas-first-api.onrender.com/
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,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.17.3",
"express-list-endpoints": "^7.1.1",
"mongoose": "^8.15.1",
"nodemon": "^3.0.1"
}
}
}
338 changes: 335 additions & 3 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,353 @@
import cors from "cors"
import express from "express"
import listEndpoints from "express-list-endpoints"
import mongoose from "mongoose"
import dotenv from "dotenv"
import bcrypt from "bcryptjs"
import crypto from "crypto"

dotenv.config()

const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/thoughts-api"
// Connect to MongoDB
mongoose.connect(mongoUrl)



// 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 || 8082
const app = express()

// Add middlewares to enable cors and json body parsing
app.use(cors())
app.use(express.json())

// Start defining your routes here

const thoughtSchema = new mongoose.Schema({
message: {
type: String,
required: true,
minlength: 5,
maxlength: 140,
},
hearts: {
type: Number,
default: 0,
},
category: {
type: String,
enum: ["happy", "family", "friends", "pets", "nature", "funny", "gratitude", "other"],
default: "other",
},
date: {
type: Date,
default: Date.now,
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: false,
},
})

const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
minlength: 3,
max_length: 20,
},
password: {
type: String,
required: true,
minlength: 6,
max_length: 50,
},
email: {
type: String,
required: true,
unique: true,
match: /.+\@.+\..+/,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool that you challenged yourself to use regex here :)

},
accessToken: {
type: String,
default: () => crypto.randomBytes(128).toString("hex"),
},
})

const authenticationUser = async (req, res, next) => {
const user = await User.findOne({ accessToken: req.headers.authorization })
if (user) {
req.user = user
next()
} else {
res.status(401).json({ loggedOut: true, message: "Unauthorized" })
}
}

const Thought = mongoose.model("Thought", thoughtSchema)
const User = mongoose.model("User", userSchema)



app.get("/", (req, res) => {
res.send("Hello Technigo!")
const endpoints = listEndpoints(app)
res.json({
message: "Welcome to the Happy thoughts API",
endpoints: endpoints
})
})

app.get("/thoughts", async (req, res) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just a tip that I got from Simon and that I found quite useful for decluttering the code and making it visually more readable, which personally helps me a lot, but there is obviously no right or wrong here. I just thought I would share the tip in case you hadn't considered it:

Instead of having all your async await functions in your server.js file, you could consider extracting them into separate files and just importing them from those files into your server.js. So instead your app.get route to fetch all your thoughts could look like this:

app.get("/thoughts", getThoughts)

And then you could have your getThoughts.js file in a folder with all of your other functions.

const { message, hearts, category, date } = req.query
let query = {}
if (message) {
query.message = message.toLowerCase()
}
if (hearts) {
query.hearts = hearts
}
if (category) {
query.category = category.toLowerCase()
}
if (date) {
query.date = { $gte: new Date(date) }
}
try {
const filteredThoughts = await Thought.find(query)
.sort({ date: -1 })
.populate("createdBy", "username")
if (filteredThoughts.length === 0) {
return res.status(404).json({
success: false,
response: [],
message: "No messages found matching the criteria. Please try different filters."
})
}
res.status(200).json({
success: true,
response: filteredThoughts,
message: "Messages found successfully"
})
} catch (error) {
console.error("Error fetching messages:", error)
res.status(500).json({
success: false,
response: error,
message: "An error occurred while fetching messages"
})
}
})


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({
success: false,
message: "Thought not found"
})
}
res.status(200).json({
success: true,
response: thought,
message: "Thought found successfully"
})
} catch (error) {
res.status(500).json({
success: false,
response: error,
message: "An error occurred while fetching the thought"
})
}
})

app.post("/users/register", async (req, res) => {
try {
const { username, password, email } = req.body

if (!username || !password || !email) {
return res.status(400).json({
success: false,
message: "Username, password and email are required"
})
}

const existingUser = await User.findOne({ $or: [{ username }, { email }] })
if (existingUser) {
return res.status(400).json({
success: false,
message: "Username or email already exists"
})
}


const user = new User({
username,
password: bcrypt.hashSync(password, 10), // Hash the password
email
})
user.save()
res.status(201).json({
id: user._id,
accessToken: user.accessToken,

})
} catch (error) {
return res.status(500).json({
success: false,
message: "An error occurred while registering the user",
error: error.message
})
}
}
)

app.post("/users/login", async (req, res) => {
const user = await User.findOne({ email: req.body.email })
if (user && bcrypt.compareSync(req.body.password, user.password)) {
res.json({ userId: user._id, accessToken: user.accessToken })
} else {
res.status(401).json({ notFound: true, message: "Invalid email or password" })
}
})



app.post("/thoughts", authenticationUser, async (req, res) => {
const { message, hearts, category, date, createdBy } = req.body

try {
const newThought = await new Thought({
message,
hearts,
category,
date,
createdBy
}).save()
res.status(201).json({
success: true,
response: newThought,
message: "Thought created successfully"
})
} catch (error) {
res.status(500).json({
success: false,
response: error,
message: "An error occurred while creating the thought"
})
}
})

app.post("/thoughts/:id/like", async (req, res) => {
try {
const { id } = req.params
const updatedThought = await Thought.findByIdAndUpdate(
id,
{ $inc: { hearts: 1 } },
{ new: true }
)
if (!updatedThought) {
return res.status(404).json({ success: false, message: "Thought not found" })
}
res.status(200).json({ success: true, response: updatedThought })
} catch (error) {
console.error("Error liking thought:", error)
res.status(500).json({ success: false, message: "Error liking thought" })
}
})


app.delete("/thoughts/:id", authenticationUser, async (req, res) => {
const { id } = req.params
try {

const thought = await Thought.findById(id)
if (!thought) {
return res.status(404).json({
success: false,
response: null,
message: "Thought not found"
})
}

if (thought.createdBy.toString() !== req.user._id.toString()) {
return res.status(403).json({
success: false,
response: null,
message: "You are not authorized to delete this thought"
})
}


const deletedThought = await Thought.findByIdAndDelete(id)

res.status(200).json({
success: true,
response: deletedThought,
message: "Thought deleted successfully"
})
} catch (error) {
res.status(500).json({
success: false,
response: error,
message: "An error occurred while deleting the thought"
})
}
})

app.put("/thoughts/:id", authenticationUser, async (req, res) => {
const { id } = req.params
const { message, category } = req.body
try {
const thought = await Thought.findById(id)
if (!thought) {
return res.status(404).json({
success: false,
response: null,
message: "Thought not found"
})
}
if (thought.createdBy.toString() !== req.user._id.toString()) {
return res.status(403).json({
success: false,
response: null,
message: "You are not authorized to update this thought"
})
}

const updateData = {}
if (message) updateData.message = message
if (category) updateData.category = category

const updatedThought = await Thought.findByIdAndUpdate(
id, updateData,
{ new: true, runValidators: true })


res.status(200).json({
success: true,
response: updatedThought,
message: "Thought updated successfully"
})
} catch (error) {
res.status(500).json({
success: false,
response: error,
message: "An error occurred while updating the thought"
})
}
})



// Start the server
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`)
Expand Down