diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 00000000000..2aae6384626 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,12 @@ +module.exports = { + extends: "eslint:recommended", + parserOptions: { + ecmaVersion: 2020, + sourceType: "module", + }, + env: { + browser: true, + node: true, + }, +}; + diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index d7993327f51..00000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = { - env: { - commonjs: true, - es2021: true, - node: true, - }, - extends: ['standard', 'prettier'], - parserOptions: { - ecmaVersion: 12, - }, - rules: {}, -} diff --git a/app.js b/app.js index 40fd9bc167f..4798fddf46a 100644 --- a/app.js +++ b/app.js @@ -1,25 +1,33 @@ -const express = require('express') -const logger = require('morgan') -const cors = require('cors') +import express from "express"; +import logger from "morgan"; +import cors from "cors"; -const contactsRouter = require('./routes/api/contacts') +import contactsRouter from './routes/api/contacts.js'; -const app = express() +const app = express(); -const formatsLogger = app.get('env') === 'development' ? 'dev' : 'short' +// Setăm formatul pentru logger în funcție de mediul de rulare +const formatsLogger = app.get('env') === 'development' ? 'dev' : 'short'; -app.use(logger(formatsLogger)) -app.use(cors()) -app.use(express.json()) +// Middleware +app.use(logger(formatsLogger)); +app.use(cors()); +app.use(express.json()); -app.use('/api/contacts', contactsRouter) +// Rutele pentru contactele API +app.use('/api/contacts', contactsRouter); +// Middleware pentru rutele nevalide 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 }) -}) +// Middleware pentru gestionarea erorilor +app.use((err, req, res) => { + const status = err.status || 500; + const message = status === 500 ? 'Internal Server Error' : err.message; + res.status(status).json({ message }); +}); + +export default app; -module.exports = app diff --git a/models/contacts.js b/models/contacts.js index 409d11c7c09..0e15bb0676c 100644 --- a/models/contacts.js +++ b/models/contacts.js @@ -1,19 +1,58 @@ -// const fs = require('fs/promises') +import fs from "fs/promises"; +import path from "path"; -const listContacts = async () => {} +const contactsPath = path.resolve("models", "contacts.json"); -const getContactById = async (contactId) => {} - -const removeContact = async (contactId) => {} - -const addContact = async (body) => {} - -const updateContact = async (contactId, body) => {} - -module.exports = { +const ContactsService = { listContacts, getContactById, - removeContact, addContact, + removeContact, updateContact, +}; + +async function listContacts() { + const data = await fs.readFile(contactsPath, "utf-8"); + return JSON.parse(data); +} + +async function getContactById(id) { + const contacts = await listContacts(); + return contacts.find((contact) => contact.id === id); +} + +async function addContact(newContact) { + const contacts = await listContacts(); + const contact = { id: Date.now().toString(), ...newContact }; + contacts.push(contact); + await saveContactsToFile(contacts); + return contact; } + +async function removeContact(id) { + const contacts = await listContacts(); + const index = contacts.findIndex((contact) => contact.id === id); + if (index === -1) return null; + const [deletedContact] = contacts.splice(index, 1); + await saveContactsToFile(contacts); + return deletedContact; +} + +async function updateContact(id, updatedData) { + const contacts = await listContacts(); + const index = contacts.findIndex((contact) => contact.id === id); + if (index === -1) return null; + contacts[index] = { ...contacts[index], ...updatedData }; + await saveContactsToFile(contacts); + return contacts[index]; +} + +async function saveContactsToFile(contacts) { + await fs.writeFile(contactsPath, JSON.stringify(contacts, null, 2), "utf-8"); +} + +export default ContactsService; + + + + diff --git a/models/contacts.json b/models/contacts.json index a21679132de..294a141ed76 100644 --- a/models/contacts.json +++ b/models/contacts.json @@ -55,8 +55,14 @@ }, { "id": "rsKkOQUi80UsgVPCcLZZW", - "name": "Alec Howard", - "email": "Donec.elementum@scelerisquescelerisquedui.net", - "phone": "(748) 206-2688" + "name": "Updated Name", + "email": "john.doe@example.com", + "phone": "1234567890" + }, + { + "id": "1732362678654", + "name": "Updated Name", + "email": "john.doe@example.com", + "phone": "1234567890" } -] +] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e6d047044e5..04c8aff0e36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "cors": "2.8.5", "cross-env": "7.0.3", "express": "4.17.1", + "joi": "^17.13.3", "morgan": "1.10.0" }, "devDependencies": { @@ -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", @@ -2166,6 +2203,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", @@ -3757,6 +3807,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 +5350,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..a6e300cfb5a 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,10 @@ "cors": "2.8.5", "cross-env": "7.0.3", "express": "4.17.1", + "joi": "^17.13.3", "morgan": "1.10.0" }, + "type": "module", "devDependencies": { "eslint": "7.19.0", "eslint-config-prettier": "^8.3.0", diff --git a/routes/api/contacts.js b/routes/api/contacts.js index a60ebd69231..5ae9cc709ad 100644 --- a/routes/api/contacts.js +++ b/routes/api/contacts.js @@ -1,25 +1,95 @@ -const express = require('express') +import express from "express"; +import Joi from "joi"; +import ContactsService from "../../models/contacts.js"; -const router = express.Router() +const router = express.Router(); -router.get('/', async (req, res, next) => { - res.json({ message: 'template message' }) -}) +// Schema Joi pentru POST +const contactSchema = Joi.object({ + name: Joi.string().min(3).max(30).required(), + email: Joi.string().email().required(), + phone: Joi.string().pattern(/^[0-9]{10}$/).required(), +}); -router.get('/:contactId', async (req, res, next) => { - res.json({ message: 'template message' }) -}) +// Schema Joi pentru PUT (actualizare parțială) +const updateContactSchema = Joi.object({ + name: Joi.string().min(3).max(30), + email: Joi.string().email(), + phone: Joi.string().pattern(/^[0-9]{10}$/), +}).min(1); -router.post('/', async (req, res, next) => { - res.json({ message: 'template message' }) -}) -router.delete('/:contactId', async (req, res, next) => { - res.json({ message: 'template message' }) -}) +// GET /api/contacts +router.get("/", async (req, res) => { + try { + const contacts = await ContactsService.listContacts(); + res.status(200).json(contacts); + } catch (error) { + res.status(500).json({ message: error.message }); + } +}); + +// GET /api/contacts/:id +router.get("/:id", async (req, res) => { + try { + const contact = await ContactsService.getContactById(req.params.id); + if (!contact) { + return res.status(404).json({ message: "Not found" }); + } + res.status(200).json(contact); + } catch (error) { + res.status(500).json({ message: error.message }); + } +}); + +// POST /api/contacts +router.post("/", async (req, res) => { + console.log(req.body); // Depanare + const { error } = contactSchema.validate(req.body); + if (error) { + return res.status(400).json({ message: error.details[0].message }); + } + try { + const newContact = await ContactsService.addContact(req.body); + res.status(201).json(newContact); + } catch (error) { + res.status(500).json({ message: error.message }); + } +}); + +// DELETE /api/contacts/:id +router.delete("/:id", async (req, res) => { + try { + const deletedContact = await ContactsService.removeContact(req.params.id); + if (!deletedContact) { + return res.status(404).json({ message: "Not found" }); + } + res.status(200).json({ message: "contact deleted" }); + } catch (error) { + res.status(500).json({ message: error.message }); + } +}); + +// PUT /api/contacts/:id +router.put("/:id", async (req, res) => { + console.log(req.body); // Depanare + const { error } = updateContactSchema.validate(req.body, { allowUnknown: true }); + if (error) { + return res.status(400).json({ message: error.details[0].message }); + } + try { + const updatedContact = await ContactsService.updateContact(req.params.id, req.body); + if (!updatedContact) { + return res.status(404).json({ message: "Not found" }); + } + res.status(200).json(updatedContact); + } catch (error) { + res.status(500).json({ message: error.message }); + } +}); + + +export default router; + -router.put('/:contactId', async (req, res, next) => { - res.json({ message: 'template message' }) -}) -module.exports = router diff --git a/server.js b/server.js index fc4e4c6bb3a..bc8f0c90fcd 100644 --- a/server.js +++ b/server.js @@ -1,4 +1,4 @@ -const app = require("./app"); +import app from"./app.js"; app.listen(3000, () => { console.log("Server is running. Use our API on port: 3000");