diff --git a/models/contacts.js b/models/contacts.js index 409d11c7c09..372279deb68 100644 --- a/models/contacts.js +++ b/models/contacts.js @@ -1,14 +1,120 @@ -// const fs = require('fs/promises') +const { v4: uuidv4 } = require("uuid"); +const fs = require('fs').promises; +const path = require('path'); +const contactsPath = path.join(__dirname, 'contacts.json'); -const listContacts = async () => {} +const listContacts = async () => { + try { + const data = await fs.readFile(contactsPath); + const contacts = JSON.parse(data); + return contacts; + } catch (err) { + console.error("Error reading contacts file in listContacts:", err); + throw err; + } +}; -const getContactById = async (contactId) => {} +const getContactById = async (contactId) => { + let contacts; + + try { + const data = await fs.readFile(contactsPath); + contacts = JSON.parse(data); + } catch (err) { + console.error("Error reading contacts file in getContactById:", err); + throw err; + } -const removeContact = async (contactId) => {} + const contact = contacts.filter((contact) => contact.id === contactId); + if (contact.length === 0) { + return null; + } + return contact; +}; -const addContact = async (body) => {} +const removeContact = async (contactId) => { + let contacts; -const updateContact = async (contactId, body) => {} + try { + contacts = await listContacts(); + } catch (err) { + console.error("Error reading contacts file in removeContact:", err); + throw err; + } + + const index = contacts.findIndex((contact) => contact.id === contactId); + if (index === -1) { + return null; + } + + contacts.splice(index, 1); + + try { + await fs.writeFile(contactsPath, JSON.stringify(contacts, null, 2)); + } catch (err) { + console.error("Error writing to contacts file in removeContact:", err); + throw err; + } + + return contacts; +}; + +const addContact = async (body) => { + let contacts; + + try { + contacts = await listContacts(); + } catch (err) { + console.error("Error reading contacts file in addContact:", err); + throw err; + } + + const newContact = { + id: uuidv4(), + ...body, + }; + + contacts.push(newContact); + + try { + await fs.writeFile(contactsPath, JSON.stringify(contacts, null, 2)); + return newContact; + } catch (err) { + console.error("Error writing to contacts file in addContact:", err); + throw err; + } +}; + +const updateContact = async (contactId, body) => { + let contacts; + + try { + contacts = await listContacts(); + } catch (err) { + console.error("Error reading contacts file in updateContact:", err); + throw err; + } + + const index = contacts.findIndex((contact) => contact.id === contactId); + if (index === -1) { + return null; + } + + const newContact = { + id: uuidv4(), + ...body, + }; + + contacts.splice(index, 1, newContact); + + try { + await fs.writeFile(contactsPath, JSON.stringify(contacts, null, 2)); + return newContact; + } catch (err) { + console.error("Error writing to contacts file in updateContact:", err); + throw err; + } +}; module.exports = { listContacts, diff --git a/package-lock.json b/package-lock.json index e6d047044e5..69d0c59d6cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,9 @@ "cors": "2.8.5", "cross-env": "7.0.3", "express": "4.17.1", - "morgan": "1.10.0" + "joi": "^17.13.3", + "morgan": "1.10.0", + "uuid": "^11.1.0" }, "devDependencies": { "eslint": "7.19.0", @@ -141,6 +143,42 @@ "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==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", + "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==", + "license": "BSD-3-Clause" + }, + "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==", + "license": "BSD-3-Clause" + }, "node_modules/@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -2166,6 +2204,19 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@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 +3595,19 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/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 +3821,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.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "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 +5364,18 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, + "joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "requires": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@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 +6433,11 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, + "uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==" + }, "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..380978dcdbc 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,9 @@ "cors": "2.8.5", "cross-env": "7.0.3", "express": "4.17.1", - "morgan": "1.10.0" + "joi": "^17.13.3", + "morgan": "1.10.0", + "uuid": "^11.1.0" }, "devDependencies": { "eslint": "7.19.0", diff --git a/routes/api/contacts.js b/routes/api/contacts.js index a60ebd69231..7f72e6374d2 100644 --- a/routes/api/contacts.js +++ b/routes/api/contacts.js @@ -1,25 +1,127 @@ +const Joi = require("joi"); +const contactsFunctions = require("../../models/contacts.js"); + +const schema = Joi.object({ + name: Joi.string().required(), + email: Joi.string() + .email({ + minDomainSegments: 2, + tlds: { allow: ["com", "net"] }, + }) + .required(), + phone: Joi.number().integer().positive().required(), +}); + + + + const express = require('express') const router = express.Router() router.get('/', async (req, res, next) => { - res.json({ message: 'template message' }) -}) + try { + const contacts = await contactsFunctions.listContacts(); + res.json({ + status: 200, + data: { contacts }, + }); + } catch (err) { + console.error(err); + next(err); + } +}); router.get('/:contactId', async (req, res, next) => { - res.json({ message: 'template message' }) -}) + try { + const contactId = req.params.contactId; + const contact = await contactsFunctions.getContactById(contactId); + if (contact) { + res.json({ + status: 200, + data: { contact }, + }); + } else { + res.status(404).json({ + status: 404, + message: "Not found", + }); + } + } catch (err) { + console.error(err); + next(err); + } +}); router.post('/', async (req, res, next) => { - res.json({ message: 'template message' }) -}) + const { error } = schema.validate(req.body); + if (error) { + res.status(400).json({ + status: 400, + message: error.message, + }); + } else { + try { + const newContact = await contactsFunctions.addContact(req.body); + res.json({ + status: 201, + data: { newContact }, + }); + } catch (err) { + console.error(err); + next(err); + } + } +}); router.delete('/:contactId', async (req, res, next) => { - res.json({ message: 'template message' }) -}) + try { + const contactId = req.params.contactId; + const index = await contactsFunctions.removeContact(contactId); + if (index !== -1) { + res.json({ + status: 200, + message: "contact deleted", + }); + } else { + res.status(404).json({ + status: 404, + message: "Not found", + }); + } + } catch (err) { + console.error(err); + next(err); + } +}); router.put('/:contactId', async (req, res, next) => { - res.json({ message: 'template message' }) -}) + const contactId = req.params.contactId; + const { error } = schema.validate(req.body); + if (error) { + res.status(400).json({ + status: 400, + message: error.message, + }); + } else { + try { + const newContact = await contactsFunctions.updateContact(contactId, req.body); + if (newContact) { + res.json({ + status: 200, + data: { newContact }, + }); + } else { + res.status(404).json({ + status: 404, + message: "Not found", + }); + } + } catch (err) { + console.error(err); + next(err); + } + } +}); module.exports = router