diff --git a/app.js b/app.js index 40fd9bc167f..f8a77e9817f 100644 --- a/app.js +++ b/app.js @@ -1,25 +1,26 @@ -const express = require('express') -const logger = require('morgan') -const cors = require('cors') +const express = require("express"); +const logger = require("morgan"); +const cors = require("cors"); -const contactsRouter = require('./routes/api/contacts') +const contactsRouter = require("./routes/api/contactsRouter"); -const app = express() +const app = express(); -const formatsLogger = app.get('env') === 'development' ? 'dev' : 'short' +const formatsLogger = app.get("env") === "development" ? "dev" : "short"; -app.use(logger(formatsLogger)) -app.use(cors()) -app.use(express.json()) +app.use(logger(formatsLogger)); +app.use(cors()); +app.use(express.json()); -app.use('/api/contacts', contactsRouter) +app.use("/api/contacts", contactsRouter); app.use((req, res) => { - res.status(404).json({ message: 'Not found' }) -}) + res.status(404).json({ message: "Not found" }); +}); app.use((err, req, res, next) => { - res.status(500).json({ message: err.message }) -}) + const { status = 500, message = "server error" } = err; + res.status(status).json({ message }); +}); -module.exports = app +module.exports = app; diff --git a/controllers/contacts/add.js b/controllers/contacts/add.js new file mode 100644 index 00000000000..a1337f5af91 --- /dev/null +++ b/controllers/contacts/add.js @@ -0,0 +1,15 @@ +const contactOperations = require("../../models/contacts"); + +const add = async (req, res) => { + const { body } = req; + const addedContact = await contactOperations.addContact(body); + res.status(201).json({ + status: "success", + code: 201, + data: { + result: addedContact, + }, + }); +}; + +module.exports = add; diff --git a/controllers/contacts/getAll.js b/controllers/contacts/getAll.js new file mode 100644 index 00000000000..ea50d842732 --- /dev/null +++ b/controllers/contacts/getAll.js @@ -0,0 +1,14 @@ +const contactOperations = require("../../models/contacts"); + +const getAll = async (req, res) => { + const allContact = await contactOperations.listContacts(); + res.json({ + status: "success", + code: 200, + data: { + result: allContact, + }, + }); +}; + +module.exports = getAll; diff --git a/controllers/contacts/getById.js b/controllers/contacts/getById.js new file mode 100644 index 00000000000..8d5b9dd9aa6 --- /dev/null +++ b/controllers/contacts/getById.js @@ -0,0 +1,20 @@ +const contactOperations = require("../../models/contacts"); + +const getById = async (req, res) => { + const { contactId } = req.params; + const contact = await contactOperations.getContactById(contactId); + if (!contact) { + const error = new Error(`contact whith id = ${contactId} not found`); + error.status = 404; + throw error; + } + res.json({ + status: "success", + code: 200, + data: { + result: contact, + }, + }); +}; + +module.exports = getById; diff --git a/controllers/contacts/index.js b/controllers/contacts/index.js new file mode 100644 index 00000000000..5cfca5a8182 --- /dev/null +++ b/controllers/contacts/index.js @@ -0,0 +1,13 @@ +const getAll = require("./getAll"); +const getById = require("./getById"); +const add = require("./add"); +const remove = require("./remove"); +const update = require("./update"); + +module.exports = { + getAll, + getById, + add, + remove, + update, +}; diff --git a/controllers/contacts/remove.js b/controllers/contacts/remove.js new file mode 100644 index 00000000000..c8f119eb3bc --- /dev/null +++ b/controllers/contacts/remove.js @@ -0,0 +1,20 @@ +const contactOperations = require("../../models/contacts"); + +const remove = async (req, res) => { + const { contactId } = req.params; + const contactToDelete = await contactOperations.removeContact(contactId); + if (!contactToDelete) { + const error = new Error(`contact whith id = ${contactId} not found`); + error.status = 404; + throw error; + } + res.json({ + status: "success", + code: 200, + data: { + result: contactToDelete, + }, + }); +}; + +module.exports = remove; diff --git a/controllers/contacts/update.js b/controllers/contacts/update.js new file mode 100644 index 00000000000..25b11904982 --- /dev/null +++ b/controllers/contacts/update.js @@ -0,0 +1,24 @@ +const contactOperations = require("../../models/contacts"); + +const update = async (req, res) => { + const { body } = req; + const { contactId } = req.params; + const contactToUpdate = await contactOperations.updateContact( + contactId, + body + ); + if (!contactToUpdate) { + const error = new Error(`contact whith id = ${contactId} not found`); + error.status = 404; + throw error; + } + res.json({ + status: "success", + code: 200, + data: { + result: contactToUpdate, + }, + }); +}; + +module.exports = update; diff --git a/controllers/index.js b/controllers/index.js new file mode 100644 index 00000000000..69c5833f507 --- /dev/null +++ b/controllers/index.js @@ -0,0 +1,5 @@ +const contactsControllers = require("./contacts"); + +module.exports = { + contactsControllers, +}; diff --git a/middlewars/cntrlWrap.js b/middlewars/cntrlWrap.js new file mode 100644 index 00000000000..5547606f5f7 --- /dev/null +++ b/middlewars/cntrlWrap.js @@ -0,0 +1,10 @@ +const cntrlWrap = (cntrl) => { + return async (req, res, next) => { + try { + await cntrl(req, res, next); + } catch (error) { + next(error); + } + }; +}; +module.exports = cntrlWrap; diff --git a/middlewars/index.js b/middlewars/index.js new file mode 100644 index 00000000000..26ba7812bae --- /dev/null +++ b/middlewars/index.js @@ -0,0 +1,7 @@ +const validation = require("./validation"); +const cntrlWrap = require("./cntrlWrap"); + +module.exports = { + validation, + cntrlWrap, +}; diff --git a/middlewars/validation.js b/middlewars/validation.js new file mode 100644 index 00000000000..7863b6c5c69 --- /dev/null +++ b/middlewars/validation.js @@ -0,0 +1,12 @@ +const validation = (schema) => { + return (req, res, next) => { + const { body } = req; + const { error } = schema.validate(body); + if (error) { + error.status = 400; + next(error); + } + next(); + }; +}; +module.exports = validation; diff --git a/models/contacts.js b/models/contacts.js index 409d11c7c09..1477cbb0666 100644 --- a/models/contacts.js +++ b/models/contacts.js @@ -1,14 +1,53 @@ -// const fs = require('fs/promises') +const fs = require("fs/promises"); +const path = require("path"); +const { v4 } = require("uuid"); -const listContacts = async () => {} +const contactsPath = path.join(__dirname, "/contacts.json"); -const getContactById = async (contactId) => {} +const listContacts = async () => { + const data = await fs.readFile(contactsPath); + const contacts = await JSON.parse(data); + return contacts; +}; -const removeContact = async (contactId) => {} +const getContactById = async (contactId) => { + const data = await listContacts(); + const contact = data.find((item) => item.id === contactId); + if (!contact) { + return null; + } + return contact; +}; -const addContact = async (body) => {} +const removeContact = async (contactId) => { + const data = await listContacts(); + const idx = data.findIndex((item) => item.id === contactId); + if (idx === -1) { + return null; + } + const updatedContacts = data.filter((_, index) => index !== idx); + await fs.writeFile(contactsPath, JSON.stringify(updatedContacts)); + return data[idx]; +}; -const updateContact = async (contactId, body) => {} +const addContact = async (body) => { + const result = await listContacts(); + const addedContact = { ...body, id: v4() }; + result.push(addedContact); + await fs.writeFile(contactsPath, JSON.stringify(result)); + return addedContact; +}; + +const updateContact = async (contactId, body) => { + const result = await listContacts(); + const idx = result.findIndex((item) => item.id === contactId); + if (idx === -1) { + return null; + } + result[idx] = { ...body, id: contactId }; + await fs.writeFile(contactsPath, JSON.stringify(result)); + return result[idx]; +}; module.exports = { listContacts, @@ -16,4 +55,4 @@ module.exports = { removeContact, addContact, updateContact, -} +}; diff --git a/models/contacts.json b/models/contacts.json index 3f22d286b9c..d7097074687 100644 --- a/models/contacts.json +++ b/models/contacts.json @@ -1,9 +1,9 @@ [ { - "id": "1", - "name": "Allen Raymond", - "email": "nulla.ante@vestibul.co.uk", - "phone": "(992) 914-3792" + "name": "Allen Limo", + "email": "Allen.ante@vestibul.co.uk", + "phone": "(992) 914-3792", + "id": "1" }, { "id": "2", diff --git a/package-lock.json b/package-lock.json index e6d047044e5..45aa387217d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,19 @@ { - "name": "template", + "name": "nodejs-contacts-hw2", "version": "0.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "template", + "name": "nodejs-contacts-hw2", "version": "0.0.0", "dependencies": { "cors": "2.8.5", "cross-env": "7.0.3", "express": "4.17.1", - "morgan": "1.10.0" + "joi": "^17.7.0", + "morgan": "1.10.0", + "uuid": "^9.0.0" }, "devDependencies": { "eslint": "7.19.0", @@ -141,6 +143,37 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "node_modules/@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -2166,6 +2199,18 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, + "node_modules/joi": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.7.0.tgz", + "integrity": "sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3544,6 +3589,14 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -3757,6 +3810,37 @@ "strip-json-comments": "^3.1.1" } }, + "@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" + }, + "@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -5269,6 +5353,18 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, + "joi": { + "version": "17.7.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.7.0.tgz", + "integrity": "sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==", + "requires": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.0", + "@sideway/pinpoint": "^2.0.0" + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6326,6 +6422,11 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, + "uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + }, "v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", diff --git a/package.json b/package.json index 5045e827160..4f7a28e77ee 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "template", + "name": "nodejs-contacts-hw2", "version": "0.0.0", "private": true, "scripts": { @@ -12,7 +12,9 @@ "cors": "2.8.5", "cross-env": "7.0.3", "express": "4.17.1", - "morgan": "1.10.0" + "joi": "^17.7.0", + "morgan": "1.10.0", + "uuid": "^9.0.0" }, "devDependencies": { "eslint": "7.19.0", diff --git a/routes/api/contactsRouter.js b/routes/api/contactsRouter.js new file mode 100644 index 00000000000..17692668d47 --- /dev/null +++ b/routes/api/contactsRouter.js @@ -0,0 +1,23 @@ +const express = require("express"); + +const { contactsControllers: cntr } = require("../../controllers"); +const { validation, cntrlWrap } = require("../../middlewars"); +const { contactsSchema } = require("../../schemas"); + +const contactsRouter = express.Router(); + +contactsRouter.get("/", cntrlWrap(cntr.getAll)); + +contactsRouter.get("/:contactId", cntrlWrap(cntr.getById)); + +contactsRouter.post("/", validation(contactsSchema), cntrlWrap(cntr.add)); + +contactsRouter.delete("/:contactId", cntrlWrap(cntr.remove)); + +contactsRouter.put( + "/:contactId", + validation(contactsSchema), + cntrlWrap(cntr.update) +); + +module.exports = contactsRouter; diff --git a/schemas/contacts.js b/schemas/contacts.js new file mode 100644 index 00000000000..05183adf09b --- /dev/null +++ b/schemas/contacts.js @@ -0,0 +1,9 @@ +const Joi = require("joi"); + +const schema = Joi.object({ + name: Joi.string().required(), + email: Joi.string().email().required(), + phone: Joi.string().required(), +}); + +module.exports = schema; \ No newline at end of file diff --git a/schemas/index.js b/schemas/index.js new file mode 100644 index 00000000000..67413fb75eb --- /dev/null +++ b/schemas/index.js @@ -0,0 +1,5 @@ +const contactsSchema = require("./contacts"); + +module.exports = { + contactsSchema, +}; diff --git a/server.js b/server.js index db330824656..231339bb8f0 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,7 @@ -const app = require('./app') +const app = require("./app"); -app.listen(3000, () => { - console.log("Server running. Use our API on port: 3000") -}) +const { PORT = 3000 } = process.env; + +app.listen(PORT, () => { + console.log("Server running. Use our API on port: 3000"); +});