-
Notifications
You must be signed in to change notification settings - Fork 31
Therese API Happy thought #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
64eaf33
3d38753
651a36d
b9b2243
7e79165
cccd86d
62b7a45
e58efd8
59a38de
66fcfb4
880d458
528f915
3f632a1
1797a85
c15c0a2
95a5ff7
0364b03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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: /.+\@.+\..+/, | ||
| }, | ||
| 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) => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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:
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 | ||
| }) | ||
| 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}`) | ||
|
|
||
There was a problem hiding this comment.
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 :)