From 62c0a7cb55eba3211243a698401a0381f68b23d0 Mon Sep 17 00:00:00 2001 From: Olaoluwa Oyebode Date: Fri, 2 Aug 2024 01:18:23 +0100 Subject: [PATCH] Olaoluwa Oyebode_Pull Request --- package-lock.json | 114 ++++++++++++++++++++++----------------- package.json | 4 +- src/index.js | 2 +- src/routers/books.js | 124 ++++++++++++++++++++++++++++++++++++++++++- src/routers/films.js | 112 ++++++++++++++++++++++++++++++++++++++ src/routers/users.js | 102 +++++++++++++++++++++++++++++++++++ src/server.js | 12 ++++- 7 files changed, 414 insertions(+), 56 deletions(-) diff --git a/package-lock.json b/package-lock.json index fc9d54a..47997e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ }, "devDependencies": { "jest": "^28.1.3", - "nodemon": "^2.0.22", + "nodemon": "^3.1.4", "supertest": "^6.3.3" } }, @@ -1395,12 +1395,12 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -1408,7 +1408,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -1428,12 +1428,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1719,9 +1719,9 @@ "dev": true }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -1992,16 +1992,16 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -2054,9 +2054,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -3512,18 +3512,18 @@ "dev": true }, "node_modules/nodemon": { - "version": "2.0.22", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz", - "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", + "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==", "dev": true, "dependencies": { "chokidar": "^3.5.2", - "debug": "^3.2.7", + "debug": "^4", "ignore-by-default": "^1.0.1", "minimatch": "^3.1.2", "pstree.remy": "^1.1.8", - "semver": "^5.7.1", - "simple-update-notifier": "^1.0.7", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", "supports-color": "^5.5.0", "touch": "^3.1.0", "undefsafe": "^2.0.5" @@ -3532,7 +3532,7 @@ "nodemon": "bin/nodemon.js" }, "engines": { - "node": ">=8.10.0" + "node": ">=10" }, "funding": { "type": "opencollective", @@ -3540,12 +3540,20 @@ } }, "node_modules/nodemon/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dev": true, "dependencies": { - "ms": "^2.1.1" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, "node_modules/nodemon/node_modules/has-flag": { @@ -3558,18 +3566,21 @@ } }, "node_modules/nodemon/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "node_modules/nodemon/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/nodemon/node_modules/supports-color": { @@ -3914,9 +3925,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -4151,24 +4162,27 @@ "dev": true }, "node_modules/simple-update-notifier": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz", - "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", "dev": true, "dependencies": { - "semver": "~7.0.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=8.10.0" + "node": ">=10" } }, "node_modules/simple-update-notifier/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "bin": { "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/sisteransi": { diff --git a/package.json b/package.json index c63bc01..041bc3e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "express-router-with-in-memory-data-store", "version": "1.0.0", "description": "", - "main": "index.js", + "main": "src/index.js", "scripts": { "start": "npx nodemon src/index.js", "test": "npx jest -i test/api/routes --forceExit --runInBand", @@ -15,7 +15,7 @@ }, "devDependencies": { "jest": "^28.1.3", - "nodemon": "^2.0.22", + "nodemon": "^3.1.4", "supertest": "^6.3.3" }, "keywords": [] diff --git a/src/index.js b/src/index.js index c16707f..bec7eca 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,5 @@ /* REQUIRE APP */ -const app = require('./server.js') +const app = require("./server.js"); const port = 3030; /* START SERVER */ diff --git a/src/routers/books.js b/src/routers/books.js index 18b9a7c..029eb1c 100644 --- a/src/routers/books.js +++ b/src/routers/books.js @@ -1,4 +1,124 @@ -// Import data here... +const express = require("express"); +const router = express.Router(); +const books = [ + { id: 1, title: "1984", type: "fiction", author: "George Orwell", pages: 5 }, + { + id: 2, + title: "Life of Pi", + type: "fiction", + author: "Yann Martel", + pages: 4, + }, + { + id: 3, + title: "How to Win Friends and Influence People", + type: "non-fiction", + author: "Dale Carnegie", + pages: 3, + }, + { + id: 4, + title: "The Lean Startup", + type: "non-fiction", + author: "Eric Reis", + pages: 2, + }, +]; -// Write routes here... +// Endpoint to get all books +router.get("/", (req, res) => { + res.json({ books }); +}); + +// Endpoint to get a book by ID +router.get("/:id", (req, res) => { + const book = books.find((b) => b.id === parseInt(req.params.id)); + if (!book) { + return res + .status(404) + .send({ error: "A book the provided ID does not exist" }); + } + res.json({ book }); +}); + +// Endpoint to create a new book +router.post("/", (req, res) => { + const { title, type, author, pages } = req.body; + if (!title || !type || !author || pages === undefined) { + return res.status(400).send({ error: "Missing fields in request body" }); + } + const existingBook = books.find((b) => b.title === title); + if (existingBook) { + return res + .status(409) + .send({ error: "A book with the provided title already exists" }); + } + const newBook = { + id: books.length + 1, + title, + type, + author, + pages, + }; + books.push(newBook); + res.status(201).json({ book: newBook }); +}); + +// Endpoint to update a book by ID +router.put("/:id", (req, res) => { + const { title, type, author, pages } = req.body; + if (!title || !type || !author || pages === undefined) { + return res.status(400).send({ error: "Missing fields in request body" }); + } + const book = books.find((b) => b.id === parseInt(req.params.id)); + if (!book) { + return res + .status(404) + .send({ error: "A book with the provided ID does not exist" }); + } + const existingBook = books.find((b) => b.title === title && b.id !== book.id); + if (existingBook) { + return res + .status(409) + .send({ error: "A book with the provided title already exists" }); + } + Object.assign(book, req.body); + res.status(200).json({ book }); +}); + +// Endpoint to partially update a book by ID +router.patch("/:id", (req, res) => { + const book = books.find((b) => b.id === parseInt(req.params.id)); + if (!book) { + return res + .status(404) + .send({ error: "A book with the provided ID does not exist" }); + } + if (req.body.title) { + const existingBook = books.find( + (b) => b.title === req.body.title && b.id !== book.id + ); + if (existingBook) { + return res + .status(409) + .send({ error: "A book with the provided title already exists" }); + } + } + Object.assign(book, req.body); + res.status(200).json({ book }); +}); + +// Endpoint to delete a book by ID +router.delete("/:id", (req, res) => { + const bookIndex = books.findIndex((b) => b.id === parseInt(req.params.id)); + if (bookIndex === -1) { + return res + .status(404) + .send({ error: "A book the provided ID does not exist" }); + } + const deletedBook = books.splice(bookIndex, 1); + res.status(200).json({ book: deletedBook[0] }); +}); + +module.exports = router; diff --git a/src/routers/films.js b/src/routers/films.js index e69de29..fe0441a 100644 --- a/src/routers/films.js +++ b/src/routers/films.js @@ -0,0 +1,112 @@ +const express = require("express"); +const router = express.Router(); + +const films = [ + { id: 1, title: "Bonnie and Clyde", director: "Arthur Penn" }, + { id: 2, title: "Reservoir Dogs", director: "Quentin Tarantino" }, + { id: 3, title: "Inception", director: "Christopher Nolan" }, + { id: 4, title: "Django Unchained", director: "Quentin Tarantino" }, +]; + +// Endpoint to get all films or filter by director +router.get("/", (req, res) => { + const { director } = req.query; + if (director) { + const directorFilms = films.filter( + (f) => f.director.toLowerCase() === director.toLowerCase() + ); + res.json({ films: directorFilms }); + } else { + res.json({ films }); + } +}); + +// Endpoint to get a film by ID +router.get("/:id", (req, res) => { + const film = films.find((f) => f.id === parseInt(req.params.id)); + if (!film) { + return res + .status(404) + .send({ error: "A film with provided ID does not exist" }); + } + res.json({ film }); +}); + +// Endpoint to create a new film +router.post("/", (req, res) => { + const { title, director } = req.body; + if (!title || !director) { + return res.status(400).send({ error: "Missing fields in request body" }); + } + const existingFilm = films.find((f) => f.title === title); + if (existingFilm) { + return res + .status(409) + .send({ error: "A film with the provided title already exists" }); + } + const newFilm = { + id: films.length + 1, + title, + director, + }; + films.push(newFilm); + res.status(201).json({ film: newFilm }); +}); + +// Endpoint to update a film by ID +router.put("/:id", (req, res) => { + const { title, director } = req.body; + if (!title || !director) { + return res.status(400).send({ error: "Missing fields in request body" }); + } + const film = films.find((f) => f.id === parseInt(req.params.id)); + if (!film) { + return res + .status(404) + .send({ error: "A film with provided ID does not exist" }); + } + const existingFilm = films.find((f) => f.title === title && f.id !== film.id); + if (existingFilm) { + return res + .status(409) + .send({ error: "A film with the provided title already exists" }); + } + Object.assign(film, req.body); + res.status(200).json({ film }); +}); + +// Endpoint to partially update a film by ID +router.patch("/:id", (req, res) => { + const film = films.find((f) => f.id === parseInt(req.params.id)); + if (!film) { + return res + .status(404) + .send({ error: "A film with provided ID does not exist" }); + } + if (req.body.title) { + const existingFilm = films.find( + (f) => f.title === req.body.title && f.id !== film.id + ); + if (existingFilm) { + return res + .status(409) + .send({ error: "A film with the provided title already exists" }); + } + } + Object.assign(film, req.body); + res.status(200).json({ film }); +}); + +// Endpoint to delete a film by ID +router.delete("/:id", (req, res) => { + const filmIndex = films.findIndex((f) => f.id === parseInt(req.params.id)); + if (filmIndex === -1) { + return res + .status(404) + .send({ error: "A film with provided ID does not exist" }); + } + const deletedFilm = films.splice(filmIndex, 1); + res.status(200).json({ film: deletedFilm[0] }); +}); + +module.exports = router; diff --git a/src/routers/users.js b/src/routers/users.js index e69de29..07a427f 100644 --- a/src/routers/users.js +++ b/src/routers/users.js @@ -0,0 +1,102 @@ +const express = require("express"); +const router = express.Router(); + +const users = [ + { id: 1, email: "edward@mail.com" }, + { id: 2, email: "nathan@mail.com" }, + { id: 3, email: "mike@mail.com" }, +]; + +// Endpoint to get all users +router.get("/", (req, res) => { + res.json({ users }); +}); + +// Endpoint to get a user by ID +router.get("/:id", (req, res) => { + const user = users.find((u) => u.id === parseInt(req.params.id)); + if (!user) { + return res + .status(404) + .send({ error: "A user with the provided ID does not exist" }); + } + res.json({ user }); +}); + +// Endpoint to create a new user +router.post("/", (req, res) => { + const { email } = req.body; + if (!email) { + return res.status(400).send({ error: "Missing fields in request body" }); + } + const existingUser = users.find((u) => u.email === email); + if (existingUser) { + return res + .status(409) + .send({ error: "A user with the provided email already exists" }); + } + const newUser = { + id: users.length + 1, + email, + }; + users.push(newUser); + res.status(201).json({ user: newUser }); +}); + +// Endpoint to update a user by ID +router.put("/:id", (req, res) => { + const { email } = req.body; + if (!email) { + return res.status(400).send({ error: "Missing fields in request body" }); + } + const user = users.find((u) => u.id === parseInt(req.params.id)); + if (!user) { + return res + .status(404) + .send({ error: "A user with the provided ID does not exist" }); + } + const existingUser = users.find((u) => u.email === email && u.id !== user.id); + if (existingUser) { + return res + .status(409) + .send({ error: "A user with the provided email already exists" }); + } + Object.assign(user, req.body); + res.status(200).json({ user }); +}); + +// Endpoint to partially update a user by ID +router.patch("/:id", (req, res) => { + const user = users.find((u) => u.id === parseInt(req.params.id)); + if (!user) { + return res + .status(404) + .send({ error: "A user with the provided ID does not exist" }); + } + if (req.body.email) { + const existingUser = users.find( + (u) => u.email === req.body.email && u.id !== user.id + ); + if (existingUser) { + return res + .status(409) + .send({ error: "A user with the provided email already exists" }); + } + } + Object.assign(user, req.body); + res.status(200).json({ user }); +}); + +// Endpoint to delete a user by ID +router.delete("/:id", (req, res) => { + const userIndex = users.findIndex((u) => u.id === parseInt(req.params.id)); + if (userIndex === -1) { + return res + .status(404) + .send({ error: "A user with the provided ID does not exist" }); + } + const deletedUser = users.splice(userIndex, 1); + res.status(200).json({ user: deletedUser[0] }); +}); + +module.exports = router; diff --git a/src/server.js b/src/server.js index 715321f..3c14f16 100644 --- a/src/server.js +++ b/src/server.js @@ -11,8 +11,18 @@ app.use(morgan("dev")); // REQUIRE ROUTERS const usersRouter = require("./routers/users"); +const booksRouter = require("./routers/books"); +const filmsRouter = require("./routers/films"); // ADD ROUTERS TO APP +app.use("/books", booksRouter); +app.use("/films", filmsRouter); +app.use("/users", usersRouter); +// Global error handler +app.use((err, req, res, next) => { + console.error(err.stack); + res.status(500).send({ error: "Something went wrong!" }); +}); -module.exports = app +module.exports = app;