diff --git a/controllers/contactsController.js b/controllers/contactsController.js new file mode 100644 index 00000000000..7089a1f63c4 --- /dev/null +++ b/controllers/contactsController.js @@ -0,0 +1,44 @@ +const contacts = require("../models/contacts"); + +async function getAll(req, res) { + const data = await contacts.listContacts(); + res.status(200).json(data); +} + +async function getById(req, res) { + const contact = await contacts.getById(req.params.id); + if (!contact) return res.status(404).json({ message: "Not found" }); + res.status(200).json(contact); +} + +async function create(req, res) { + const { name, email, phone } = req.body; + if (!name || !email || !phone) { + return res.status(400).json({ message: "missing required name field" }); + } + const newContact = await contacts.addContact(req.body); + res.status(201).json(newContact); +} + +async function remove(req, res) { + const result = await contacts.removeContact(req.params.id); + if (!result) return res.status(404).json({ message: "Not found" }); + res.status(200).json({ message: "contact deleted" }); +} + +async function update(req, res) { + if (!req.body || Object.keys(req.body).length === 0) { + return res.status(400).json({ message: "missing fields" }); + } + const updated = await contacts.updateContact(req.params.id, req.body); + if (!updated) return res.status(404).json({ message: "Not found" }); + res.status(200).json(updated); +} + +module.exports = { + getAll, + getById, + create, + remove, + update, +}; diff --git a/models/contacts.json b/db/contacts.json similarity index 100% rename from models/contacts.json rename to db/contacts.json diff --git a/models/contacts.js b/models/contacts.js index 409d11c7c09..8eab33be2c8 100644 --- a/models/contacts.js +++ b/models/contacts.js @@ -1,19 +1,49 @@ -// const fs = require('fs/promises') +const fs = require("fs/promises"); +const path = require("path"); +const { v4: uuidv4 } = require("uuid"); -const listContacts = async () => {} +const contactsPath = path.join(__dirname, "../db/contacts.json"); -const getContactById = async (contactId) => {} +async function listContacts() { + const data = await fs.readFile(contactsPath); + return JSON.parse(data); +} -const removeContact = async (contactId) => {} +async function getById(id) { + const contacts = await listContacts(); + return contacts.find((contact) => contact.id === id) || null; +} -const addContact = async (body) => {} +async function addContact({ name, email, phone }) { + const contacts = await listContacts(); + const newContact = { id: uuidv4(), name, email, phone }; + contacts.push(newContact); + await fs.writeFile(contactsPath, JSON.stringify(contacts, null, 2)); + return newContact; +} -const updateContact = async (contactId, body) => {} +async function removeContact(id) { + const contacts = await listContacts(); + const index = contacts.findIndex((c) => c.id === id); + if (index === -1) return null; + const deleted = contacts.splice(index, 1); + await fs.writeFile(contactsPath, JSON.stringify(contacts, null, 2)); + return deleted[0]; +} + +async function updateContact(id, body) { + const contacts = await listContacts(); + const index = contacts.findIndex((c) => c.id === id); + if (index === -1) return null; + contacts[index] = { ...contacts[index], ...body }; + await fs.writeFile(contactsPath, JSON.stringify(contacts, null, 2)); + return contacts[index]; +} module.exports = { listContacts, - getContactById, - removeContact, + getById, addContact, + removeContact, updateContact, -} +}; diff --git a/package-lock.json b/package-lock.json index e6d047044e5..c034f2e5ac1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,10 +8,11 @@ "name": "template", "version": "0.0.0", "dependencies": { - "cors": "2.8.5", + "cors": "^2.8.5", "cross-env": "7.0.3", - "express": "4.17.1", - "morgan": "1.10.0" + "express": "^4.17.1", + "joi": "^17.13.3", + "morgan": "^1.10.0" }, "devDependencies": { "eslint": "7.19.0", @@ -141,6 +142,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", @@ -677,6 +714,7 @@ "version": "2.8.5", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", "dependencies": { "object-assign": "^4", "vary": "^1" @@ -1354,6 +1392,7 @@ "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "license": "MIT", "dependencies": { "accepts": "~1.3.7", "array-flatten": "1.1.1", @@ -2166,6 +2205,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", @@ -2401,6 +2453,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "license": "MIT", "dependencies": { "basic-auth": "~2.0.1", "debug": "2.6.9", @@ -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.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 +5353,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", diff --git a/package.json b/package.json index 5045e827160..8ce93cdabaf 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,11 @@ "lint:fix": "eslint --fix **/*.js" }, "dependencies": { - "cors": "2.8.5", + "cors": "^2.8.5", "cross-env": "7.0.3", - "express": "4.17.1", - "morgan": "1.10.0" + "express": "^4.17.1", + "joi": "^17.13.3", + "morgan": "^1.10.0" }, "devDependencies": { "eslint": "7.19.0", diff --git a/readme.md b/readme.md index 67351cfcdbd..3750be63fc9 100644 --- a/readme.md +++ b/readme.md @@ -1,14 +1,29 @@ ## GoIT Node.js Course Template Homework -Please fork this repository to complete your homework assignments (2-6). -Forking will create a repository on your http://github.com account. +Realizează un fork al acestui repozitoriu pentru a îndeplini temele de acasă (2-6). Fork-ul va crea un repozitoriu pe contul tău de pe http://github.com -## Expalanation Recording to hanle homeworks in the Node.js Block -[Watch the video](https://www.loom.com/share/007c97d271604e02ae61adbb5b69edd3) +Adaugă mentorul la colaborare. -### Commands: +Pentru fiecare temă, creează un branch separat. -- `npm start` — starts the server in production mode. -- `npm run start:dev` — starts the server in development mode. -- `npm run lint` — runs eslint to check the code. Make sure to execute this before each PR and fix all linting errors. -- `npm lint:fix` — same as the previous command but fixes simple linting errors automatically. +- hw02 +- hw03 +- hw04 +- hw05 +- hw06 + +Fiecare branch nou pentru fiecare temă trebuie să fie derivat din branch-ul principal (master). + +După ce ai terminat lucrul la tema de acasă în branch-ul tău, trebuie să creezi un pull request (PR). Apoi, adaugă mentorul pentru revizuirea codului. Abia după ce mentorul aprobă PR-ul, poți face "merge" a branch-ului cu tema de acasă în branch-ul master. + +Citește cu atenție comentariile mentorului. Corectează observațiile și fă un "commit" în branch-ul cu tema de acasă. Modificările se vor reflecta automat în PR după ce trimiți "commit"-ul cu corecțiile pe GitHub. După corectare, adaugă din nou mentorul pentru revizuirea codului. + +- La predarea temei de acasă, este furnizat un link către PR. +- Codul JavaScript este curat și ușor de înțeles, iar pentru formatare se folosește Prettier. + +### Comenzi: + +- `npm start` — pornește serverul în modul production. +- `npm run start:dev` — pornește serverul în modul dezvoltare (development). +- `npm run lint` — rulează verificarea codului cu ESLint, este necesar să se ruleze înaintea fiecărui PR și să se corecteze toate erorile linterului. +- `npm lint:fix` — aceeași verificare a linterului, dar cu corecții automate pentru erorile simple. diff --git a/routes/api/contacts.js b/routes/api/contacts.js index a60ebd69231..e4eab75f632 100644 --- a/routes/api/contacts.js +++ b/routes/api/contacts.js @@ -1,25 +1,22 @@ -const express = require('express') +const express = require("express"); +const router = express.Router(); +const controller = require("../controllers/contactsController"); +const { addSchema, updateSchema } = require("../validators/contactValidator"); -const router = express.Router() +function validate(schema) { + return (req, res, next) => { + const { error } = schema.validate(req.body); + if (error) { + return res.status(400).json({ message: error.message }); + } + next(); + }; +} -router.get('/', async (req, res, next) => { - res.json({ message: 'template message' }) -}) +router.get("/", controller.getAll); +router.get("/:id", controller.getById); +router.post("/", validate(addSchema), controller.create); +router.delete("/:id", controller.remove); +router.put("/:id", validate(updateSchema), controller.update); -router.get('/:contactId', async (req, res, next) => { - res.json({ message: 'template message' }) -}) - -router.post('/', async (req, res, next) => { - res.json({ message: 'template message' }) -}) - -router.delete('/:contactId', async (req, res, next) => { - res.json({ message: 'template message' }) -}) - -router.put('/:contactId', async (req, res, next) => { - res.json({ message: 'template message' }) -}) - -module.exports = router +module.exports = router; diff --git a/validators/contactsValidators.js b/validators/contactsValidators.js new file mode 100644 index 00000000000..ca56ddc7735 --- /dev/null +++ b/validators/contactsValidators.js @@ -0,0 +1,15 @@ +const Joi = require("joi"); + +const addSchema = Joi.object({ + name: Joi.string().required(), + email: Joi.string().email().required(), + phone: Joi.string().required(), +}); + +const updateSchema = Joi.object({ + name: Joi.string(), + email: Joi.string().email(), + phone: Joi.string(), +}).min(1); + +module.exports = { addSchema, updateSchema };