From 35ea83a111f8f5da2e55e0ce3100f6d43769f0aa Mon Sep 17 00:00:00 2001 From: Kathinka Sewell <4355167+kathinka@users.noreply.github.com> Date: Sun, 19 May 2024 10:30:54 +0200 Subject: [PATCH 1/6] Update description for API backend --- model/Happythoughts.js | 23 ++++++ package.json | 10 +-- routes/Routes.js | 159 +++++++++++++++++++++++++++++++++++++++++ server.js | 30 ++++---- 4 files changed, 204 insertions(+), 18 deletions(-) create mode 100644 model/Happythoughts.js create mode 100644 routes/Routes.js diff --git a/model/Happythoughts.js b/model/Happythoughts.js new file mode 100644 index 00000000..2887206e --- /dev/null +++ b/model/Happythoughts.js @@ -0,0 +1,23 @@ +import mongoose from "mongoose"; + +const { Schema } = mongoose; +const happyThoughtSchema = new Schema({ + message: { + type: String, + required: true, + minlength: 5, + maxlength: 140, + }, + hearts: { + type: Number, + default: 0, + }, + createdAt: { + type: Date, + default: Date.now, + }, +}); + +const HappyThought = mongoose.model("HappyThought", happyThoughtSchema); + +export default HappyThought; diff --git a/package.json b/package.json index 1c371b45..d9f08d72 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "project-happy-thoughts-api", "version": "1.0.0", - "description": "Starter project to get up and running with express quickly", + "description": "API backend for happy thoughts project", "scripts": { "start": "babel-node server.js", "dev": "nodemon server.js --exec babel-node" @@ -13,8 +13,10 @@ "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", "cors": "^2.8.5", - "express": "^4.17.3", - "mongoose": "^8.0.0", + "express": "^4.19.2", + "express-list-endpoints": "^7.1.0", + "mongodb": "^6.6.1", + "mongoose": "^8.3.4", "nodemon": "^3.0.1" } -} \ No newline at end of file +} diff --git a/routes/Routes.js b/routes/Routes.js new file mode 100644 index 00000000..22bac2f8 --- /dev/null +++ b/routes/Routes.js @@ -0,0 +1,159 @@ +import express, { response } from "express"; +import listEndpoints from "express-list-endpoints"; +import HappyThought from "../model/Happythoughts"; +const router = express.Router(); + +// get 20 happy thoughts +router.get("/thoughts", async (req, res) => { + try { + const happyThoughts = await HappyThought.find() + .sort({ createdAt: "desc" }) + .limit(20) + .exec(); + res.json(happyThoughts); + } catch (error) { + res.status(400).json({ + success: false, + response: error, + message: "Could not get thoughts", + }); + } +}); + +//post new happy thought +router.post("/thoughts", async (req, res) => { + const { message } = req.body; + try { + const newHappyThought = await new HappyThought({ message }).save(); + res.status(201).json(newHappyThought); + } catch (error) { + console.error(error); // Log the error + res.status(400).json({ + success: false, + response: error, + message: "Could not save thought", + }); + } +}); + +//post a like to a happy thought +router.post("/thoughts/:thoughtId/like", async (req, res) => { + const { thoughtId } = req.params; + try { + const updatedThought = await HappyThought.findByIdAndUpdate( + thoughtId, + { $inc: { hearts: 1 } }, + { new: true } + ); + if (updatedThought) { + res.json(updatedThought); + } else { + res.status(404).json({ + success: false, + response: error, + message: "Thought not found", + }); + } + } catch (error) { + res.status(400).json({ + success: false, + response: error, + message: "Could not save like", + }); + } +}); + +//delete a happy thought +router.delete("/thoughts/:thoughtId", async (req, res) => { + const { thoughtId } = req.params; + try { + const deletedThought = await HappyThought.findByIdAndDelete(thoughtId); + if (deletedThought) { + res.json(deletedThought); + } else { + res.status(404).json({ + success: false, + response: error, + message: "Thought not found", + }); + } + } catch (error) { + res.status(400).json({ + success: false, + response: error, + message: "Could not delete thought", + }); + } +}); + +//filter happy thoughts by limit or skip or both +router.get("/thoughts/q", async (req, res) => { + const { limit, page } = req.query; + const thoughts = await HappyThought.find() + .sort({ createdAt: "desc" }) + .limit(parseInt(limit)) + .skip(parseInt(page)) + .exec(); + res.json(thoughts); +}); + +// get updated documentation +router.get("/", (req, res) => { + try { + const endpoints = listEndpoints(router); + const updatedEndpoints = endpoints.map((endpoint) => { + if (endpoint.path === "/thoughts/q") { + return { + path: endpoint.path, + methods: endpoint.methods, + queryParameters: [ + { + name: "limit", + description: + "filter the thoughts by the number of thoughts you want to get Example: /thoughts/q?limit=5 you could also combine limit and skip Example: /thoughts/q?limit=5&page=5", + }, + { + name: "page", + description: + "filter the thoughts by the number of thoughts you want to skip Example: /thoughts/q?page=5 you could also combine limit and skip Example: /thoughts/q?limit=5&page=5", + }, + ], + }; + } + return { + path: endpoint.path, + methods: endpoint.methods, + }; + }); + res.json(updatedEndpoints); + } catch (error) { + // If an error occurred, create a new error with a custom message + const customError = new Error( + "An error occurred while fetching the endpoints" + ); + res.status(404).json({ + success: false, + response: error, + message: customError.message, + }); + } +}); + +router.get("/pages", async (req, res) => { + const limit = Number(req.query.limit) || 20; // default limit to if not provided + + try { + const count = await HappyThought.countDocuments(); // replace Thought with your model + const totalPages = Math.ceil(count / limit); + + res.json({ totalPages }); + } catch (err) { + res.status(500).json({ message: err.message }); + } +}); + +router.use("/", (req, res) => { + res.send(listEndpoints(router)); +}); + +export default router; diff --git a/server.js b/server.js index dfe86fb8..0da23485 100644 --- a/server.js +++ b/server.js @@ -1,25 +1,27 @@ -import cors from "cors"; import express from "express"; +import cors from "cors"; import mongoose from "mongoose"; +import router from "./routes/Routes.js"; -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/project-mongo"; -mongoose.connect(mongoUrl); -mongoose.Promise = Promise; - -// 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 -app.use(cors()); +app.use( + cors({ + origin: ["https://happyhappenings.netlify.app"], + methods: ["GET", "POST", "PUT", "DELETE"], + allowedHeaders: ["Content-Type", "Authorization", "updatedthoughtlike"], + }) +); app.use(express.json()); +app.use("/", router); -// Start defining your routes here -app.get("/", (req, res) => { - res.send("Hello Technigo!"); -}); +// Defines the port the app will run on. +const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/happyThoughts"; +mongoose.connect(mongoUrl); +mongoose.Promise = Promise; + +const port = process.env.PORT || 8090; // Start the server app.listen(port, () => { From 1e32fd209a20f4d0fc6102aff7814ce623da4dd2 Mon Sep 17 00:00:00 2001 From: Kathinka Sewell <4355167+kathinka@users.noreply.github.com> Date: Sun, 19 May 2024 10:43:35 +0200 Subject: [PATCH 2/6] updated readme and pull request template --- README.md | 13 +++++++------ pull_request_template.md | 7 +++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 6a75d8e1..db10d40d 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,14 @@ # Project Happy Thoughts API -Replace this readme with your own information about your project. - -Start by briefly describing the assignment in a sentence or two. Keep it short and to the point. +made my own api for the happy thoughts site we built in week 7 ## The problem +Started working in the [template](https://github.com/kathinka/express-api-starter), and copied my work over into this repo. -Describe how you approached to problem, and what tools and techniques you used to solve it. How did you plan? What technologies did you use? If you had more time, what would be next? +Started building the API as I have done on the last tasks +wanted to add more to is as well, som made endpoint for it, but did not complete it as I rather wanted to join my team on working on the final task. So I revisit this later when I have more time! ## 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. +View it live: +- [API](https://happy-thoughts-api-ap6c.onrender.com/) +- [Client](https://happyhappenings.netlify.app/) \ No newline at end of file diff --git a/pull_request_template.md b/pull_request_template.md index d92c89b5..48fb1fac 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,7 +1,6 @@ ## Netlify link -Add your Netlify link here. -PS. Don't forget to add it in your readme as well. +https://happy-thoughts-api-ap6c.onrender.com +https://happyhappenings.netlify.app/ ## Collaborators -Add your collaborators here. Write their GitHub usernames in square brackets. If there's more than one, separate them with a comma, like this: -[github-username-1, github-username-2] +solo From ced33998273f1b16d6704ea531cbf98a0353d297 Mon Sep 17 00:00:00 2001 From: Kathinka Sewell <4355167+kathinka@users.noreply.github.com> Date: Sun, 19 May 2024 11:01:30 +0200 Subject: [PATCH 3/6] Update API link in README and pull request template --- README.md | 2 +- pull_request_template.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index db10d40d..a5ee8e45 100644 --- a/README.md +++ b/README.md @@ -10,5 +10,5 @@ wanted to add more to is as well, som made endpoint for it, but did not complete ## View it live View it live: -- [API](https://happy-thoughts-api-ap6c.onrender.com/) +- [API](https://project-happy-thoughts-api-sw6f.onrender.com/) - [Client](https://happyhappenings.netlify.app/) \ No newline at end of file diff --git a/pull_request_template.md b/pull_request_template.md index 48fb1fac..06b4226b 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,6 +1,6 @@ ## Netlify link -https://happy-thoughts-api-ap6c.onrender.com -https://happyhappenings.netlify.app/ +[API](https://project-happy-thoughts-api-sw6f.onrender.com/) +[Site](https://happyhappenings.netlify.app/) ## Collaborators solo From d0e32db2d5604b132e1a78b32c16e26811762a4c Mon Sep 17 00:00:00 2001 From: Kathinka Sewell <4355167+kathinka@users.noreply.github.com> Date: Sat, 22 Jun 2024 13:06:17 +0200 Subject: [PATCH 4/6] Update API link in README and pull request template --- model/Happythoughts.js | 2 +- routes/Routes.js | 21 +++++++++++++++------ server.js | 5 +++-- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/model/Happythoughts.js b/model/Happythoughts.js index 2887206e..4b781ab9 100644 --- a/model/Happythoughts.js +++ b/model/Happythoughts.js @@ -20,4 +20,4 @@ const happyThoughtSchema = new Schema({ const HappyThought = mongoose.model("HappyThought", happyThoughtSchema); -export default HappyThought; +export default HappyThought; \ No newline at end of file diff --git a/routes/Routes.js b/routes/Routes.js index 22bac2f8..9be75b07 100644 --- a/routes/Routes.js +++ b/routes/Routes.js @@ -3,6 +3,8 @@ import listEndpoints from "express-list-endpoints"; import HappyThought from "../model/Happythoughts"; const router = express.Router(); + + // get 20 happy thoughts router.get("/thoughts", async (req, res) => { try { @@ -20,12 +22,15 @@ router.get("/thoughts", async (req, res) => { } }); + + + //post new happy thought router.post("/thoughts", async (req, res) => { const { message } = req.body; try { const newHappyThought = await new HappyThought({ message }).save(); - res.status(201).json(newHappyThought); + res.status(201).json( newHappyThought); } catch (error) { console.error(error); // Log the error res.status(400).json({ @@ -102,7 +107,7 @@ router.get("/", (req, res) => { try { const endpoints = listEndpoints(router); const updatedEndpoints = endpoints.map((endpoint) => { - if (endpoint.path === "/thoughts/q") { + if (endpoint.path === "/thoughts") { return { path: endpoint.path, methods: endpoint.methods, @@ -110,12 +115,12 @@ router.get("/", (req, res) => { { name: "limit", description: - "filter the thoughts by the number of thoughts you want to get Example: /thoughts/q?limit=5 you could also combine limit and skip Example: /thoughts/q?limit=5&page=5", + "filter the thoughts by the number of thoughts you want to get Example: /thoughts/q?limit=5 you could also combine limit and skip Example: /thoughts?limit=5&page=5", }, { name: "page", description: - "filter the thoughts by the number of thoughts you want to skip Example: /thoughts/q?page=5 you could also combine limit and skip Example: /thoughts/q?limit=5&page=5", + "filter the thoughts by the number of thoughts you want to skip Example: /thoughts/q?page=5 you could also combine limit and skip Example: /thoughts?limit=5&page=5", }, ], }; @@ -139,7 +144,8 @@ router.get("/", (req, res) => { } }); -router.get("/pages", async (req, res) => { + +router.get('/pages', async (req, res) => { const limit = Number(req.query.limit) || 20; // default limit to if not provided try { @@ -152,8 +158,11 @@ router.get("/pages", async (req, res) => { } }); + + + router.use("/", (req, res) => { res.send(listEndpoints(router)); }); -export default router; +export default router; \ No newline at end of file diff --git a/server.js b/server.js index 0da23485..072b9ef0 100644 --- a/server.js +++ b/server.js @@ -9,6 +9,7 @@ const app = express(); app.use( cors({ origin: ["https://happyhappenings.netlify.app"], + methods: ["GET", "POST", "PUT", "DELETE"], allowedHeaders: ["Content-Type", "Authorization", "updatedthoughtlike"], }) @@ -21,9 +22,9 @@ const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/happyThoughts"; mongoose.connect(mongoUrl); mongoose.Promise = Promise; -const port = process.env.PORT || 8090; +const port = process.env.PORT || 8080; // Start the server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); -}); +}); \ No newline at end of file From 14a91ae3e98b006f0e4ffe42d705230ad729244b Mon Sep 17 00:00:00 2001 From: Kathinka Sewell <4355167+kathinka@users.noreply.github.com> Date: Sat, 22 Jun 2024 23:01:33 +0200 Subject: [PATCH 5/6] fixed routes, and updated endpoint documentation --- routes/Routes.js | 146 ++++++++++++++++++++++++++--------------------- server.js | 2 +- 2 files changed, 82 insertions(+), 66 deletions(-) diff --git a/routes/Routes.js b/routes/Routes.js index 9be75b07..4c08cd6c 100644 --- a/routes/Routes.js +++ b/routes/Routes.js @@ -1,16 +1,14 @@ -import express, { response } from "express"; +import express from "express"; import listEndpoints from "express-list-endpoints"; import HappyThought from "../model/Happythoughts"; const router = express.Router(); - - -// get 20 happy thoughts +// get 100 last happy thoughts router.get("/thoughts", async (req, res) => { try { const happyThoughts = await HappyThought.find() .sort({ createdAt: "desc" }) - .limit(20) + .limit(100) .exec(); res.json(happyThoughts); } catch (error) { @@ -22,15 +20,12 @@ router.get("/thoughts", async (req, res) => { } }); - - - //post new happy thought router.post("/thoughts", async (req, res) => { const { message } = req.body; try { const newHappyThought = await new HappyThought({ message }).save(); - res.status(201).json( newHappyThought); + res.status(201).json(newHappyThought); } catch (error) { console.error(error); // Log the error res.status(400).json({ @@ -55,14 +50,12 @@ router.post("/thoughts/:thoughtId/like", async (req, res) => { } else { res.status(404).json({ success: false, - response: error, message: "Thought not found", }); } } catch (error) { res.status(400).json({ success: false, - response: error, message: "Could not save like", }); } @@ -78,14 +71,12 @@ router.delete("/thoughts/:thoughtId", async (req, res) => { } else { res.status(404).json({ success: false, - response: error, message: "Thought not found", }); } } catch (error) { res.status(400).json({ success: false, - response: error, message: "Could not delete thought", }); } @@ -93,13 +84,32 @@ router.delete("/thoughts/:thoughtId", async (req, res) => { //filter happy thoughts by limit or skip or both router.get("/thoughts/q", async (req, res) => { - const { limit, page } = req.query; - const thoughts = await HappyThought.find() - .sort({ createdAt: "desc" }) - .limit(parseInt(limit)) - .skip(parseInt(page)) - .exec(); - res.json(thoughts); + const { limit = 10, page = 1 } = req.query; // Provide default values for limit and page + const skip = (parseInt(page) - 1) * parseInt(limit); // Correctly calculate skip + + try { + const thoughts = await HappyThought.find() + .sort({ createdAt: "desc" }) + .limit(parseInt(limit)) + .skip(skip) // Skip documents that have already been fetched + .exec(); + res.json(thoughts); + } catch (error) { + res.status(500).json({ message: error.message }); + } +}); + +router.get("/pages", async (req, res) => { + const limit = Number(req.query.limit) || 5; // default limit to if not provided + + try { + const count = await HappyThought.countDocuments(); // replace Thought with your model + const totalPages = Math.ceil(count / limit); + + res.json({ totalPages }); + } catch (err) { + res.status(500).json({ message: err.message }); + } }); // get updated documentation @@ -107,62 +117,68 @@ router.get("/", (req, res) => { try { const endpoints = listEndpoints(router); const updatedEndpoints = endpoints.map((endpoint) => { - if (endpoint.path === "/thoughts") { - return { - path: endpoint.path, - methods: endpoint.methods, - queryParameters: [ - { - name: "limit", - description: - "filter the thoughts by the number of thoughts you want to get Example: /thoughts/q?limit=5 you could also combine limit and skip Example: /thoughts?limit=5&page=5", - }, - { - name: "page", - description: - "filter the thoughts by the number of thoughts you want to skip Example: /thoughts/q?page=5 you could also combine limit and skip Example: /thoughts?limit=5&page=5", - }, - ], - }; + switch (endpoint.path) { + case "/thoughts": + return { + path: endpoint.path, + methods: endpoint.methods, + description: + "Get the last 100 happy thoughts or post a new happy thought.", + }; + case "/thoughts/q": + return { + path: endpoint.path, + methods: endpoint.methods, + description: "Filter happy thoughts by limit or skip or both.", + queryParameters: [ + { + name: "limit", + description: + "Limit the number of thoughts returned. Default is 5. example: /thoughts/q?limit=10. you can also use /thoughts/q?limit=5&page=2 to get the second page of 5 thoughts.", + }, + { + name: "page", + description: + "Paginate through thoughts based on limit. Default is 1. example: /thoughts/q?limit=5&page=2. ", + }, + ], + }; + case "/thoughts/:thoughtId/like": + return { + path: endpoint.path, + methods: endpoint.methods, + description: "Post a like to a happy thought.", + }; + case "/thoughts/:thoughtId": + return { + path: endpoint.path, + methods: ["DELETE"], + description: "Delete a happy thought.", + }; + case "/pages": + return { + path: endpoint.path, + methods: endpoint.methods, + description: + "Get the total number of pages of happy thoughts. Example: /pages.", + }; + default: + return { + path: endpoint.path, + methods: endpoint.methods, + }; } - return { - path: endpoint.path, - methods: endpoint.methods, - }; }); res.json(updatedEndpoints); } catch (error) { - // If an error occurred, create a new error with a custom message const customError = new Error( "An error occurred while fetching the endpoints" ); res.status(404).json({ success: false, - response: error, message: customError.message, }); } }); - -router.get('/pages', async (req, res) => { - const limit = Number(req.query.limit) || 20; // default limit to if not provided - - try { - const count = await HappyThought.countDocuments(); // replace Thought with your model - const totalPages = Math.ceil(count / limit); - - res.json({ totalPages }); - } catch (err) { - res.status(500).json({ message: err.message }); - } -}); - - - - -router.use("/", (req, res) => { - res.send(listEndpoints(router)); -}); - -export default router; \ No newline at end of file +export default router; diff --git a/server.js b/server.js index 072b9ef0..7832824b 100644 --- a/server.js +++ b/server.js @@ -27,4 +27,4 @@ const port = process.env.PORT || 8080; // Start the server app.listen(port, () => { console.log(`Server running on http://localhost:${port}`); -}); \ No newline at end of file +}); From 9f5adab44812a1a6dc2adfe219b6548e1b3adaad Mon Sep 17 00:00:00 2001 From: Kathinka Sewell <4355167+kathinka@users.noreply.github.com> Date: Sat, 22 Jun 2024 23:25:07 +0200 Subject: [PATCH 6/6] Update npm dependency to latest stable version --- package.json | 1 + server.js | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d9f08d72..476f1071 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@babel/node": "^7.16.8", "@babel/preset-env": "^7.16.11", "cors": "^2.8.5", + "dotenv": "^16.4.5", "express": "^4.19.2", "express-list-endpoints": "^7.1.0", "mongodb": "^6.6.1", diff --git a/server.js b/server.js index 7832824b..ee5cb9d1 100644 --- a/server.js +++ b/server.js @@ -2,6 +2,8 @@ import express from "express"; import cors from "cors"; import mongoose from "mongoose"; import router from "./routes/Routes.js"; +import dotenv from "dotenv"; +dotenv.config(); const app = express(); @@ -18,8 +20,8 @@ app.use(express.json()); app.use("/", router); // Defines the port the app will run on. -const mongoUrl = process.env.MONGO_URL || "mongodb://localhost/happyThoughts"; -mongoose.connect(mongoUrl); +const MONGO_URL = process.env.MONGO_URL || "mongodb://localhost/happyThoughts"; +mongoose.connect(MONGO_URL); mongoose.Promise = Promise; const port = process.env.PORT || 8080;