From 59de8f48b8b46e1e29e30ea67717684c4b3068bc Mon Sep 17 00:00:00 2001 From: SebastienBtr Date: Sat, 7 Nov 2020 15:42:04 +0100 Subject: [PATCH] implement user service + fixes --- services/auth/docker-compose.yaml | 2 - .../documentation/postman/user-postman.json | 176 +++++++++++- .../documentation/swagger/user-swagger.json | 85 +++++- services/launcher.sh | 3 +- ...encies copy.sh => upgrade-dependencies.sh} | 0 services/user/package.json | 4 +- .../user/src/controllers/createUser.ctrl.js | 12 +- .../user/src/controllers/deleteUser.ctrl.js | 9 +- .../user/src/controllers/getUserById.ctrl.js | 9 +- .../user/src/controllers/getUsers.ctrl.js | 11 +- services/user/src/controllers/index.js | 1 + .../src/controllers/updateUserById.ctrl.js | 26 +- .../controllers/updateUserPassword.ctrl.js | 37 +++ services/user/src/repository.js | 69 ++++- services/user/src/routes.js | 7 + services/user/src/tests/createUser.test.js | 15 + services/user/src/tests/getUserById.test.js | 2 - services/user/src/tests/getUsers.test.js | 27 +- .../user/src/tests/updateUserById.test.js | 14 +- .../user/src/tests/updateUserPassword.test.js | 62 ++++ services/user/yarn.lock | 267 +++++++++++++++++- specification/user/api.json | 53 +++- 22 files changed, 831 insertions(+), 60 deletions(-) rename services/{upgrade-dependencies copy.sh => upgrade-dependencies.sh} (100%) create mode 100644 services/user/src/controllers/updateUserPassword.ctrl.js create mode 100644 services/user/src/tests/updateUserPassword.test.js diff --git a/services/auth/docker-compose.yaml b/services/auth/docker-compose.yaml index 421950c..7f19895 100644 --- a/services/auth/docker-compose.yaml +++ b/services/auth/docker-compose.yaml @@ -8,8 +8,6 @@ services: - /app/node_modules networks: - app - depends_on: - - prisma logging: driver: ${LOGGING_DRIVER:-json-file} environment: diff --git a/services/documentation/postman/user-postman.json b/services/documentation/postman/user-postman.json index a05cd3a..01e1cea 100644 --- a/services/documentation/postman/user-postman.json +++ b/services/documentation/postman/user-postman.json @@ -5,7 +5,7 @@ "description" : { "content" : "Service to manage users" }, - "version" : "1.0.0" + "version" : "1.0.1" }, "item" : [ { "name" : "users", @@ -63,7 +63,7 @@ }, "header" : [ ] }, - "body" : "[ {\n \"updatedAt\" : \"2020-11-07T11:53:39.125Z\",\n \"email\" : \"20f3fe7e92c1\",\n \"lastName\" : \"bfd3faf79685\",\n \"createdAt\" : \"2020-11-07T11:53:39.125Z\",\n \"firstName\" : \"9850bebb76c8\",\n \"id\" : \"f41470bf-4bc3-43b7-90ed-cad1f5da1a1d\"\n} ]", + "body" : "[ {\n \"updatedAt\" : \"2020-11-07T14:24:30.337Z\",\n \"email\" : \"a8ed5478d629\",\n \"lastName\" : \"c4a431b5ae47\",\n \"createdAt\" : \"2020-11-07T14:24:30.337Z\",\n \"firstName\" : \"d54c1c6f10b4\",\n \"id\" : \"b22cebac-f4cb-421c-be8e-ae908a0f459c\"\n} ]", "code" : 200 }, { "name" : "Example 400 - unit", @@ -146,7 +146,7 @@ }, "header" : [ ] }, - "body" : "{\n \"updatedAt\" : \"2020-11-07T11:53:39.125Z\",\n \"email\" : \"da0d87f85268\",\n \"lastName\" : \"3e4bf8e6b14a\",\n \"createdAt\" : \"2020-11-07T11:53:39.125Z\",\n \"firstName\" : \"d333e90195c0\",\n \"id\" : \"ae391b41-b357-4e64-b5a3-9a60792af2d5\"\n}", + "body" : "{\n \"updatedAt\" : \"2020-11-07T14:24:30.337Z\",\n \"email\" : \"7db6cb21cb8f\",\n \"lastName\" : \"1a5cfaf91e56\",\n \"createdAt\" : \"2020-11-07T14:24:30.337Z\",\n \"firstName\" : \"8b040f6155f2\",\n \"id\" : \"63a291a9-82b7-40b5-bd71-cea6300ad5f6\"\n}", "code" : 200 }, { "name" : "Example 404 - unit", @@ -195,7 +195,7 @@ } } ], "body" : { - "raw" : "{\n \"firstName\" : \"2728feff8290\",\n \"lastName\" : \"4c6d4433f956\",\n \"email\" : \"336b4b563f3c\",\n \"password\" : \"b6575c2e4032\"\n}", + "raw" : "{\n \"firstName\" : \"728604980ff9\",\n \"lastName\" : \"f57955a59d33\",\n \"email\" : \"e64518fa927e\",\n \"password\" : \"f32f04ebe6d9\"\n}", "mode" : "raw" } }, @@ -232,11 +232,11 @@ } } ], "body" : { - "raw" : "{\n \"firstName\" : \"2728feff8290\",\n \"lastName\" : \"4c6d4433f956\",\n \"email\" : \"336b4b563f3c\",\n \"password\" : \"b6575c2e4032\"\n}", + "raw" : "{\n \"firstName\" : \"728604980ff9\",\n \"lastName\" : \"f57955a59d33\",\n \"email\" : \"e64518fa927e\",\n \"password\" : \"f32f04ebe6d9\"\n}", "mode" : "raw" } }, - "body" : "{\n \"updatedAt\" : \"2020-11-07T11:53:39.125Z\",\n \"email\" : \"2b172eff76da\",\n \"lastName\" : \"e8d49e227692\",\n \"createdAt\" : \"2020-11-07T11:53:39.125Z\",\n \"firstName\" : \"8344b2bc535a\",\n \"id\" : \"13f035b4-ca1f-4947-b1a8-2e582b178798\"\n}", + "body" : "{\n \"updatedAt\" : \"2020-11-07T14:24:30.337Z\",\n \"email\" : \"b205c7190385\",\n \"lastName\" : \"1dac52de474c\",\n \"createdAt\" : \"2020-11-07T14:24:30.337Z\",\n \"firstName\" : \"86fe348d5eea\",\n \"id\" : \"67ae47b2-5947-4d2f-8c3b-7ce2f37f1a2a\"\n}", "code" : 201 }, { "name" : "Example 400 - unit", @@ -260,7 +260,7 @@ } } ], "body" : { - "raw" : "{\n \"firstName\" : \"2728feff8290\",\n \"lastName\" : \"4c6d4433f956\",\n \"email\" : \"336b4b563f3c\",\n \"password\" : \"b6575c2e4032\"\n}", + "raw" : "{\n \"firstName\" : \"728604980ff9\",\n \"lastName\" : \"f57955a59d33\",\n \"email\" : \"e64518fa927e\",\n \"password\" : \"f32f04ebe6d9\"\n}", "mode" : "raw" } }, @@ -287,7 +287,7 @@ } } ], "body" : { - "raw" : "{\n \"firstName\" : \"2728feff8290\",\n \"lastName\" : \"4c6d4433f956\",\n \"email\" : \"336b4b563f3c\",\n \"password\" : \"b6575c2e4032\"\n}", + "raw" : "{\n \"firstName\" : \"728604980ff9\",\n \"lastName\" : \"f57955a59d33\",\n \"email\" : \"e64518fa927e\",\n \"password\" : \"f32f04ebe6d9\"\n}", "mode" : "raw" } }, @@ -322,7 +322,7 @@ } } ], "body" : { - "raw" : "{\n \"firstName\" : \"cc37e0e8f87d\",\n \"lastName\" : \"a9d8ad9429c1\",\n \"email\" : \"8a7be82447a0\",\n \"password\" : \"264af3ce0eb6\"\n}", + "raw" : "{\n \"firstName\" : \"23005c32346d\",\n \"lastName\" : \"1db2f6c372fc\",\n \"email\" : \"637418c94579\"\n}", "mode" : "raw" } }, @@ -366,11 +366,11 @@ } } ], "body" : { - "raw" : "{\n \"firstName\" : \"cc37e0e8f87d\",\n \"lastName\" : \"a9d8ad9429c1\",\n \"email\" : \"8a7be82447a0\",\n \"password\" : \"264af3ce0eb6\"\n}", + "raw" : "{\n \"firstName\" : \"23005c32346d\",\n \"lastName\" : \"1db2f6c372fc\",\n \"email\" : \"637418c94579\"\n}", "mode" : "raw" } }, - "body" : "{\n \"updatedAt\" : \"2020-11-07T11:53:39.126Z\",\n \"email\" : \"7d7af19a3e39\",\n \"lastName\" : \"f59051a5847b\",\n \"createdAt\" : \"2020-11-07T11:53:39.126Z\",\n \"firstName\" : \"7d61b18143f9\",\n \"id\" : \"bdd895ae-1ec0-470a-901a-e53bb68fcba0\"\n}", + "body" : "{\n \"updatedAt\" : \"2020-11-07T14:24:30.337Z\",\n \"email\" : \"6cfd14097c9f\",\n \"lastName\" : \"d78de82b5e4e\",\n \"createdAt\" : \"2020-11-07T14:24:30.337Z\",\n \"firstName\" : \"ef9b0f852423\",\n \"id\" : \"43fe22b3-5598-4ae9-bfaa-c8e7b2c70725\"\n}", "code" : 200 }, { "name" : "Example 404 - unit", @@ -401,7 +401,7 @@ } } ], "body" : { - "raw" : "{\n \"firstName\" : \"cc37e0e8f87d\",\n \"lastName\" : \"a9d8ad9429c1\",\n \"email\" : \"8a7be82447a0\",\n \"password\" : \"264af3ce0eb6\"\n}", + "raw" : "{\n \"firstName\" : \"23005c32346d\",\n \"lastName\" : \"1db2f6c372fc\",\n \"email\" : \"637418c94579\"\n}", "mode" : "raw" } }, @@ -435,7 +435,7 @@ } } ], "body" : { - "raw" : "{\n \"firstName\" : \"cc37e0e8f87d\",\n \"lastName\" : \"a9d8ad9429c1\",\n \"email\" : \"8a7be82447a0\",\n \"password\" : \"264af3ce0eb6\"\n}", + "raw" : "{\n \"firstName\" : \"23005c32346d\",\n \"lastName\" : \"1db2f6c372fc\",\n \"email\" : \"637418c94579\"\n}", "mode" : "raw" } }, @@ -469,13 +469,161 @@ } } ], "body" : { - "raw" : "{\n \"firstName\" : \"cc37e0e8f87d\",\n \"lastName\" : \"a9d8ad9429c1\",\n \"email\" : \"8a7be82447a0\",\n \"password\" : \"264af3ce0eb6\"\n}", + "raw" : "{\n \"firstName\" : \"23005c32346d\",\n \"lastName\" : \"1db2f6c372fc\",\n \"email\" : \"637418c94579\"\n}", "mode" : "raw" } }, "code" : 412 } ], "type" : "item" + }, { + "request" : { + "url" : { + "raw" : "{{BASE_URL}}/users/:id/password", + "host" : [ "{{BASE_URL}}" ], + "path" : [ "users", ":id", "password" ], + "query" : [ ], + "variable" : [ { + "key" : "id", + "value" : "{{id}}", + "description" : { + "content" : "Type: uuid | Required: true" + }, + "disabled" : false + } ] + }, + "method" : "PUT", + "description" : { + "content" : "Update the password of a user; methodName: updateUserPassword" + }, + "header" : [ { + "key" : "Content-Type", + "value" : "application/json", + "description" : { + "content" : "Required to send JSON body" + } + } ], + "body" : { + "raw" : "{\n \"password\" : \"77f1a01b6510\"\n}", + "mode" : "raw" + } + }, + "name" : "PUT /users/:id/password", + "description" : { + "content" : "Update the password of a user; methodName: updateUserPassword" + }, + "event" : [ { + "listen" : "test", + "script" : { + "exec" : [ "pm.test(\"PUT requests should return 2xx\", function () {", " pm.response.to.be.success;", "});" ], + "type" : "text/javascript" + } + } ], + "response" : [ { + "name" : "Example 200 - user", + "originalRequest" : { + "url" : { + "raw" : "{{BASE_URL}}/users/:id/password", + "host" : [ "{{BASE_URL}}" ], + "path" : [ "users", ":id", "password" ], + "query" : [ ], + "variable" : [ { + "key" : "id", + "value" : "{{id}}", + "description" : { + "content" : "Type: uuid | Required: true" + }, + "disabled" : false + } ] + }, + "method" : "PUT", + "description" : { + "content" : "Update the password of a user; methodName: updateUserPassword" + }, + "header" : [ { + "key" : "Content-Type", + "value" : "application/json", + "description" : { + "content" : "Required to send JSON body" + } + } ], + "body" : { + "raw" : "{\n \"password\" : \"77f1a01b6510\"\n}", + "mode" : "raw" + } + }, + "body" : "{\n \"updatedAt\" : \"2020-11-07T14:24:30.337Z\",\n \"email\" : \"c2a8134de22a\",\n \"lastName\" : \"4cade6e838c4\",\n \"createdAt\" : \"2020-11-07T14:24:30.337Z\",\n \"firstName\" : \"bfa178599874\",\n \"id\" : \"50675bf2-78d9-4f7e-91d8-fd4ba6844888\"\n}", + "code" : 200 + }, { + "name" : "Example 404 - unit", + "originalRequest" : { + "url" : { + "raw" : "{{BASE_URL}}/users/:id/password", + "host" : [ "{{BASE_URL}}" ], + "path" : [ "users", ":id", "password" ], + "query" : [ ], + "variable" : [ { + "key" : "id", + "value" : "{{id}}", + "description" : { + "content" : "Type: uuid | Required: true" + }, + "disabled" : false + } ] + }, + "method" : "PUT", + "description" : { + "content" : "Update the password of a user; methodName: updateUserPassword" + }, + "header" : [ { + "key" : "Content-Type", + "value" : "application/json", + "description" : { + "content" : "Required to send JSON body" + } + } ], + "body" : { + "raw" : "{\n \"password\" : \"77f1a01b6510\"\n}", + "mode" : "raw" + } + }, + "code" : 404 + }, { + "name" : "Example 400 - unit", + "originalRequest" : { + "url" : { + "raw" : "{{BASE_URL}}/users/:id/password", + "host" : [ "{{BASE_URL}}" ], + "path" : [ "users", ":id", "password" ], + "query" : [ ], + "variable" : [ { + "key" : "id", + "value" : "{{id}}", + "description" : { + "content" : "Type: uuid | Required: true" + }, + "disabled" : false + } ] + }, + "method" : "PUT", + "description" : { + "content" : "Update the password of a user; methodName: updateUserPassword" + }, + "header" : [ { + "key" : "Content-Type", + "value" : "application/json", + "description" : { + "content" : "Required to send JSON body" + } + } ], + "body" : { + "raw" : "{\n \"password\" : \"77f1a01b6510\"\n}", + "mode" : "raw" + } + }, + "code" : 400 + } ], + "type" : "item" }, { "request" : { "url" : { diff --git a/services/documentation/swagger/user-swagger.json b/services/documentation/swagger/user-swagger.json index 8a754dc..0055841 100644 --- a/services/documentation/swagger/user-swagger.json +++ b/services/documentation/swagger/user-swagger.json @@ -3,7 +3,7 @@ "info": { "title": "user", "description": "Service to manage users", - "version": "1.0.0" + "version": "1.0.1" }, "consumes": [ "application/json" @@ -42,10 +42,10 @@ "description": "Create a user; methodName: createUser", "parameters": [ { - "name": "user_form", + "name": "user_create_form", "in": "body", "schema": { - "$ref": "#/definitions/user_form" + "$ref": "#/definitions/user_create_form" } } ], @@ -100,10 +100,10 @@ "format": "uuid" }, { - "name": "user_form", + "name": "user_update_form", "in": "body", "schema": { - "$ref": "#/definitions/user_form" + "$ref": "#/definitions/user_update_form" } } ], @@ -145,6 +145,41 @@ } } } + }, + "/users/{id}/password": { + "put": { + "description": "Update the password of a user; methodName: updateUserPassword", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": "string", + "format": "uuid" + }, + { + "name": "user_password_form", + "in": "body", + "schema": { + "$ref": "#/definitions/user_password_form" + } + } + ], + "responses": { + "200": { + "description": "200 response", + "schema": { + "$ref": "#/definitions/user" + } + }, + "400": { + "description": "400 response" + }, + "404": { + "description": "404 response" + } + } + } } }, "definitions": { @@ -184,10 +219,10 @@ } } }, - "user_form": { + "user_create_form": { "type": "object", - "description": "The model used to create/update instances of users", - "title": "user_form", + "description": "The model used to create instances of users", + "title": "user_create_form", "required": [ "firstName", "lastName", @@ -208,6 +243,40 @@ "type": "string" } } + }, + "user_password_form": { + "type": "object", + "description": "The model used to update the 'password' field of a user", + "title": "user_password_form", + "required": [ + "password" + ], + "properties": { + "password": { + "type": "string" + } + } + }, + "user_update_form": { + "type": "object", + "description": "The model used to update instances of users", + "title": "user_update_form", + "required": [ + "firstName", + "lastName", + "email" + ], + "properties": { + "firstName": { + "type": "string" + }, + "lastName": { + "type": "string" + }, + "email": { + "type": "string" + } + } } } } \ No newline at end of file diff --git a/services/launcher.sh b/services/launcher.sh index c2fa5bd..39af264 100755 --- a/services/launcher.sh +++ b/services/launcher.sh @@ -26,8 +26,7 @@ then # The Auth service must be launch after user. scriptArguments=(article cart documentation user auth) fi -cd "$root"/api-gateway -ENV=$env docker-compose up -d --build --quiet-pull --remove-orphans + if [[ $env != "prod" ]] then cd "$root"/kafka diff --git a/services/upgrade-dependencies copy.sh b/services/upgrade-dependencies.sh similarity index 100% rename from services/upgrade-dependencies copy.sh rename to services/upgrade-dependencies.sh diff --git a/services/user/package.json b/services/user/package.json index 3f26771..c61800b 100644 --- a/services/user/package.json +++ b/services/user/package.json @@ -13,11 +13,11 @@ "lint-fix": "eslint ./ --ext js --fix" }, "dependencies": { + "bcrypt": "^5.0.0", "body-parser": "^1.18.3", "dotenv": "^8.2.0", "express": "^4.17.1", "express-routes-versioning": "^1.0.1", - "prisma-client-lib": "^1.34.10", "winston": "^3.2.1" }, @@ -40,4 +40,4 @@ "/generated/" ] } -} \ No newline at end of file +} diff --git a/services/user/src/controllers/createUser.ctrl.js b/services/user/src/controllers/createUser.ctrl.js index b377caf..d225a93 100644 --- a/services/user/src/controllers/createUser.ctrl.js +++ b/services/user/src/controllers/createUser.ctrl.js @@ -1,4 +1,6 @@ const winston = require('winston'); +const bcrypt = require('bcrypt'); +const { createUser, getUsers } = require('../repository'); /** * Check if the body of the request contains the good elements @@ -20,8 +22,14 @@ const bodyIsValid = (body) => { module.exports.createUser = async (req, res) => { if (bodyIsValid(req.body)) { try { - // TODO: - res.status(200).send({}); + const users = await getUsers({ email: req.body.email }); + if (users.length > 0) { + res.status(422).send({ message: 'A user with this email already exist' }); + return; + } + req.body.password = await bcrypt.hash(req.body.password, 10); + const newUser = await createUser(req.body); + res.status(201).send(newUser); } catch (e) { winston.error(e); res.status(500).send({ message: e }); diff --git a/services/user/src/controllers/deleteUser.ctrl.js b/services/user/src/controllers/deleteUser.ctrl.js index cef2f3d..93a3fa9 100644 --- a/services/user/src/controllers/deleteUser.ctrl.js +++ b/services/user/src/controllers/deleteUser.ctrl.js @@ -1,4 +1,5 @@ const winston = require('winston'); +const { deleteUser } = require('../repository'); /** * Delete a user @@ -6,8 +7,12 @@ const winston = require('winston'); */ module.exports.deleteUser = async (req, res) => { try { - // TODO: - res.status(200).send({}); + const deletedUser = await deleteUser(req.params.id); + if (deletedUser != null) { + res.status(204).send(); + } else { + res.status(404).send({ message: `No user with id: ${req.params.id}` }); + } } catch (e) { winston.error(e); res.status(500).send({ message: e }); diff --git a/services/user/src/controllers/getUserById.ctrl.js b/services/user/src/controllers/getUserById.ctrl.js index 0e97e29..fb29b17 100644 --- a/services/user/src/controllers/getUserById.ctrl.js +++ b/services/user/src/controllers/getUserById.ctrl.js @@ -1,4 +1,5 @@ const winston = require('winston'); +const { getUserById } = require('../repository'); /** * Get a user by id @@ -6,8 +7,12 @@ const winston = require('winston'); */ module.exports.getUserById = async (req, res) => { try { - // TODO: - res.status(200).send({}); + const user = await getUserById(req.params.id); + if (user != null) { + res.status(200).send(user); + } else { + res.status(404).send({ message: `No user with id: ${req.params.id}` }); + } } catch (e) { winston.error(e); res.status(500).send({ message: e }); diff --git a/services/user/src/controllers/getUsers.ctrl.js b/services/user/src/controllers/getUsers.ctrl.js index fcf14aa..00c4c12 100644 --- a/services/user/src/controllers/getUsers.ctrl.js +++ b/services/user/src/controllers/getUsers.ctrl.js @@ -1,4 +1,5 @@ const winston = require('winston'); +const { getUsers } = require('../repository'); /** * Get users @@ -6,8 +7,14 @@ const winston = require('winston'); */ module.exports.getUsers = async (req, res) => { try { - // TODO: - res.status(200).send({}); + const { email } = req.query; + + const filters = { + ...email != null && { email }, + }; + + const users = await getUsers(filters); + res.status(200).send(users); } catch (e) { winston.error(e); res.status(500).send({ message: e }); diff --git a/services/user/src/controllers/index.js b/services/user/src/controllers/index.js index 2a7dce8..6730cb7 100644 --- a/services/user/src/controllers/index.js +++ b/services/user/src/controllers/index.js @@ -3,3 +3,4 @@ exports.getUserById = require('./getUserById.ctrl').getUserById; exports.createUser = require('./createUser.ctrl').createUser; exports.updateUserById = require('./updateUserById.ctrl').updateUserById; exports.deleteUser = require('./deleteUser.ctrl').deleteUser; +exports.updateUserPassword = require('./updateUserPassword.ctrl').updateUserPassword; diff --git a/services/user/src/controllers/updateUserById.ctrl.js b/services/user/src/controllers/updateUserById.ctrl.js index e3331ae..007763f 100644 --- a/services/user/src/controllers/updateUserById.ctrl.js +++ b/services/user/src/controllers/updateUserById.ctrl.js @@ -1,13 +1,16 @@ const winston = require('winston'); +const { + updateUserById, getUsers, getUserById, +} = require('../repository'); /** * Check if the body of the request contains the good elements */ const bodyIsValid = (body) => { const { - firstName, lastName, email, password, + firstName, lastName, email, } = body; - if (!firstName || !lastName || !email || !password) { + if (!firstName || !lastName || !email) { return false; } return true; @@ -20,8 +23,23 @@ const bodyIsValid = (body) => { module.exports.updateUserById = async (req, res) => { if (bodyIsValid(req.body)) { try { - // TODO: - res.status(200).send({}); + const existingUser = await getUserById(req.params.id); + if (existingUser == null) { + res.status(404).send({ message: `No user with id: ${req.params.id}` }); + return; + } + // Check that the user is still unique + const users = await getUsers({ email: req.body.email }); + if (users.length > 0 && users[0].id !== existingUser.id) { + res.status(422).send({ message: 'A user with this email already exist' }); + return; + } + const updatedUser = await updateUserById(req.params.id, req.body); + if (updatedUser != null) { + res.status(200).send(updatedUser); + } else { + res.status(404).send({ message: `No user with id: ${req.params.id}` }); + } } catch (e) { winston.error(e); res.status(500).send({ message: e }); diff --git a/services/user/src/controllers/updateUserPassword.ctrl.js b/services/user/src/controllers/updateUserPassword.ctrl.js new file mode 100644 index 0000000..d6186d8 --- /dev/null +++ b/services/user/src/controllers/updateUserPassword.ctrl.js @@ -0,0 +1,37 @@ +const winston = require('winston'); +const bcrypt = require('bcrypt'); +const { updateUserPassword } = require('../repository'); + +/** + * Check if the body of the request contains the good elements + */ +const bodyIsValid = (body) => { + const { password } = body; + if (!password) { + return false; + } + return true; +}; + +/** + * Update the password of a user + * @see PUT /users/:id/password + */ +module.exports.updateUserPassword = async (req, res) => { + if (bodyIsValid(req.body)) { + try { + const password = await bcrypt.hash(req.body.password, 10); + const updatedUser = await updateUserPassword(req.params.id, password); + if (updatedUser != null) { + res.status(200).send(updatedUser); + } else { + res.status(404).send({ message: `No user with id: ${req.params.id}` }); + } + } catch (e) { + winston.error(e); + res.status(500).send({ message: e }); + } + } else { + res.status(400).send({ message: 'Invalid body format' }); + } +}; diff --git a/services/user/src/repository.js b/services/user/src/repository.js index a0d53a7..0adafb0 100644 --- a/services/user/src/repository.js +++ b/services/user/src/repository.js @@ -1,3 +1,70 @@ const { prisma } = require('../generated/prisma-client'); -// TODO: add your prisma queries here +// Hide password +const UserFragment = ` +fragment UserHidePassword on User { + id + firstName + lastName + email + updatedAt + createdAt +} +`; + +module.exports.createUser = data => prisma.createUser({ + firstName: data.firstName, + lastName: data.lastName, + email: data.email, + password: data.password, +}).$fragment(UserFragment); + +module.exports.deleteUser = async (id) => { + const userExists = await prisma.$exists.user({ + id, + }); + if (userExists) { + return prisma.deleteUser({ id }).$fragment(UserFragment); + } + return null; +}; + +module.exports.getUserById = id => prisma.user({ id }).$fragment(UserFragment); + +module.exports.getUsers = filters => prisma.users({ where: { ...filters } }); + +module.exports.updateUserById = async (id, data) => { + const userExists = await prisma.$exists.user({ + id, + }); + if (userExists) { + return prisma.updateUser({ + data: { + firstName: data.firstName, + lastName: data.lastName, + email: data.email, + }, + where: { + id, + }, + }).$fragment(UserFragment); + } + return null; +}; + +module.exports.updateUserPassword = async (id, password) => { + const userExists = await prisma.$exists.user({ + id, + }); + if (userExists) { + return prisma.updateUser({ + data: { + password, + }, + where: { + id, + }, + }).$fragment(UserFragment); + } + return null; +}; diff --git a/services/user/src/routes.js b/services/user/src/routes.js index a743814..58b66b9 100644 --- a/services/user/src/routes.js +++ b/services/user/src/routes.js @@ -44,4 +44,11 @@ router.delete('/users/:id', routesVersioning({ '^1.0.0': ctrl.deleteUser, })); +/** + * Update the password of a user + */ +router.put('/users/:id/password', routesVersioning({ + '^1.0.0': ctrl.updateUserPassword, +})); + module.exports = router; diff --git a/services/user/src/tests/createUser.test.js b/services/user/src/tests/createUser.test.js index 8639b6f..becf4b0 100644 --- a/services/user/src/tests/createUser.test.js +++ b/services/user/src/tests/createUser.test.js @@ -10,6 +10,13 @@ const requestBody = { password: 'password!', }; +const sampleOk = { + firstName: 'firstName', + lastName: 'lastName', + email: 'email!', + password: 'password', +}; + beforeEach(async (done) => { await prisma.deleteManyUsers(); @@ -33,6 +40,14 @@ describe('Create a user', () => { expect(res.body).toHaveProperty('updatedAt'); done(); }); + it('422 error because the user already exist', async (done) => { + await prisma.createUser(sampleOk); + const res = await request(app) + .put('/users') + .send(requestBody); + expect(res.statusCode).toEqual(422); + done(); + }); it.each( [{ lastName: 'lastName', email: 'email', password: 'password' }, { firstName: 'firstName', email: 'email', password: 'password' }, { firstName: 'firstName', lastName: 'lastName', password: 'password' }, { firstName: 'firstName', lastName: 'lastName', email: 'email' }], )('Should return a 400 because a required field is missing', async (data, done) => { diff --git a/services/user/src/tests/getUserById.test.js b/services/user/src/tests/getUserById.test.js index d1f0cf3..fdea96e 100644 --- a/services/user/src/tests/getUserById.test.js +++ b/services/user/src/tests/getUserById.test.js @@ -34,9 +34,7 @@ describe('Get a user by id', () => { expect(res.body).toHaveProperty('email'); expect(res.body.email).toEqual(sampleOk.email); expect(res.body).toHaveProperty('createdAt'); - expect(res.body.createdAt).toEqual(sampleOk.createdAt); expect(res.body).toHaveProperty('updatedAt'); - expect(res.body.updatedAt).toEqual(sampleOk.updatedAt); done(); }); it('404 error case', async (done) => { diff --git a/services/user/src/tests/getUsers.test.js b/services/user/src/tests/getUsers.test.js index e5853ca..27124ae 100644 --- a/services/user/src/tests/getUsers.test.js +++ b/services/user/src/tests/getUsers.test.js @@ -33,10 +33,33 @@ describe('Get users', () => { expect(res.body.lastName).toEqual(sampleOk.lastName); expect(res.body).toHaveProperty('email'); expect(res.body.email).toEqual(sampleOk.email); + expect(res.body).toHaveProperty('password'); + expect(res.body).toHaveProperty('createdAt'); + expect(res.body).toHaveProperty('updatedAt'); + done(); + }); + it('Users by email', async (done) => { + const data = await prisma.createUser(sampleOk); + const otherUser = { ...sampleOk }; + otherUser.email = '123'; + await prisma.createUser(otherUser); + const res = await request(app) + .get(`/users?email=${data.email}`); + expect(res.statusCode).toEqual(200); + expect(Array.isArray(res.body)).toBeTruthy(); + expect(res.body.length).toEqual(1); + [res.body] = res.body; + expect(res.body).toHaveProperty('id'); + expect(res.body.id).toEqual(data.id); + expect(res.body).toHaveProperty('firstName'); + expect(res.body.firstName).toEqual(sampleOk.firstName); + expect(res.body).toHaveProperty('lastName'); + expect(res.body.lastName).toEqual(sampleOk.lastName); + expect(res.body).toHaveProperty('email'); + expect(res.body.email).toEqual(sampleOk.email); + expect(res.body).toHaveProperty('password'); expect(res.body).toHaveProperty('createdAt'); - expect(res.body.createdAt).toEqual(sampleOk.createdAt); expect(res.body).toHaveProperty('updatedAt'); - expect(res.body.updatedAt).toEqual(sampleOk.updatedAt); done(); }); }); diff --git a/services/user/src/tests/updateUserById.test.js b/services/user/src/tests/updateUserById.test.js index ce39284..8f8beb9 100644 --- a/services/user/src/tests/updateUserById.test.js +++ b/services/user/src/tests/updateUserById.test.js @@ -42,13 +42,21 @@ describe('Update a user by id', () => { expect(res.body).toHaveProperty('email'); expect(res.body.email).toEqual(requestBody.email); expect(res.body).toHaveProperty('createdAt'); - expect(res.body.createdAt).toEqual(sampleOk.createdAt); expect(res.body).toHaveProperty('updatedAt'); - expect(res.body.updatedAt).toEqual(sampleOk.updatedAt); + done(); + }); + it('422 error because the user already exist', async (done) => { + const user = { ...sampleOk }; + user.email = requestBody.email; + await prisma.createUser(user); + const res = await request(app) + .put(`/users/${presentId}`) + .send(requestBody); + expect(res.statusCode).toEqual(422); done(); }); it.each( - [{ lastName: 'lastName', email: 'email', password: 'password' }, { firstName: 'firstName', email: 'email', password: 'password' }, { firstName: 'firstName', lastName: 'lastName', password: 'password' }, { firstName: 'firstName', lastName: 'lastName', email: 'email' }], + [{ lastName: 'lastName', email: 'email' }, { firstName: 'firstName', email: 'email' }, { firstName: 'firstName', lastName: 'lastName' }], )('Should return a 400 because a required field is missing', async (data, done) => { const res = await request(app) .put(`/users/${presentId}`) diff --git a/services/user/src/tests/updateUserPassword.test.js b/services/user/src/tests/updateUserPassword.test.js new file mode 100644 index 0000000..2281b1a --- /dev/null +++ b/services/user/src/tests/updateUserPassword.test.js @@ -0,0 +1,62 @@ +/* eslint-disable no-undef */ +const request = require('supertest'); +const app = require('../server'); +const { prisma } = require('../../generated/prisma-client'); + +const sampleOk = { + firstName: 'firstName', + lastName: 'lastName', + email: 'email', + password: 'password', +}; + +const requestBody = { + password: 'password!', +}; + +const notPresentId = '000000'; +let presentId; + +beforeEach(async (done) => { + await prisma.deleteManyUsers(); + const data = await prisma.createUser(sampleOk); + presentId = data.id; + done(); +}); + +describe('Update the password of a user', () => { + it('Nominal case', async (done) => { + const res = await request(app) + .put(`/users/${presentId}/password`) + .send(requestBody); + expect(res.statusCode).toEqual(200); + expect(res.body).toHaveProperty('id'); + expect(res.body.id).toEqual(presentId); + expect(res.body).toHaveProperty('firstName'); + expect(res.body.firstName).toEqual(sampleOk.firstName); + expect(res.body).toHaveProperty('lastName'); + expect(res.body.lastName).toEqual(sampleOk.lastName); + expect(res.body).toHaveProperty('email'); + expect(res.body.email).toEqual(sampleOk.email); + expect(res.body).toHaveProperty('createdAt'); + expect(res.body).toHaveProperty('updatedAt'); + done(); + }); + it.each( + [{ eeeoooo: 'aboer' }], + )('Should return a 400 because a required field is missing', async (data, done) => { + const res = await request(app) + .put(`/users/${notPresentId}/password`) + .send(data); + expect(res.statusCode).toEqual(400); + done(); + }); + + it('404 error case', async (done) => { + const res = await request(app) + .put(`/users/${notPresentId}/password`) + .send(requestBody); + expect(res.statusCode).toEqual(404); + done(); + }); +}); diff --git a/services/user/yarn.lock b/services/user/yarn.lock index be05338..38be4a2 100644 --- a/services/user/yarn.lock +++ b/services/user/yarn.lock @@ -680,6 +680,11 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.11.0" +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + ansi-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" @@ -745,6 +750,19 @@ apollo-utilities@^1.3.0: ts-invariant "^0.4.0" tslib "^1.10.0" +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -945,6 +963,14 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" +bcrypt@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.0.0.tgz#051407c7cd5ffbfb773d541ca3760ea0754e37e2" + integrity sha512-jB0yCBl4W/kVHM2whjfyqnxTmOHkCX4kHEa5nYKSoGeYe8YrjTYTc87/6bwt1g8cmV0QrbhKriETg9jWtcREhg== + dependencies: + node-addon-api "^3.0.0" + node-pre-gyp "0.15.0" + binary-extensions@^1.0.0: version "1.13.1" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" @@ -1147,6 +1173,11 @@ chokidar@^2.1.8: optionalDependencies: fsevents "^1.2.7" +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + ci-info@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" @@ -1198,6 +1229,11 @@ co@^4.6.0: resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + collect-v8-coverage@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" @@ -1308,6 +1344,11 @@ confusing-browser-globals@^1.0.5: resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz#30d1e7f3d1b882b25ec4933d1d1adac353d20a59" integrity sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA== +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + contains-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" @@ -1524,6 +1565,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -1534,6 +1580,11 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -2203,6 +2254,13 @@ fresh@0.5.2: resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= +fs-minipass@^1.2.5: + version "1.2.7" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" + integrity sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA== + dependencies: + minipass "^2.6.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2231,6 +2289,20 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + gensync@^1.0.0-beta.1: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -2385,6 +2457,11 @@ has-symbols@^1.0.1: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -2485,7 +2562,7 @@ human-signals@^1.1.1: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== -iconv-lite@0.4.24, iconv-lite@^0.4.24: +iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -2497,6 +2574,13 @@ ignore-by-default@^1.0.1: resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk= +ignore-walk@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.3.tgz#017e2447184bfeade7c238e4aefdd1e8f95b1e37" + integrity sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw== + dependencies: + minimatch "^3.0.4" + ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -2701,6 +2785,13 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" @@ -3705,6 +3796,21 @@ minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.9.0.tgz#e713762e7d3e32fed803115cf93e04bca9fcc9a6" + integrity sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.2.1: + version "1.3.3" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.3.3.tgz#2290de96818a34c29551c8a8d301216bd65a861d" + integrity sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q== + dependencies: + minipass "^2.9.0" + mixin-deep@^1.2.0: version "1.3.2" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" @@ -3713,7 +3819,7 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@^0.5.1: +mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -3767,6 +3873,15 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +needle@^2.5.0: + version "2.5.2" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.5.2.tgz#cf1a8fce382b5a280108bba90a14993c00e4010a" + integrity sha512-LbRIwS9BfkPvNwNHlsA41Q29kL2L/6VaOJ0qisM5lLWsTV3nP15abO5ITL6L81zqFhzjRKDAYjpcBcwM0AVvLQ== + dependencies: + debug "^3.2.6" + iconv-lite "^0.4.4" + sax "^1.2.4" + negotiator@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" @@ -3777,6 +3892,11 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== +node-addon-api@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.0.2.tgz#04bc7b83fd845ba785bb6eae25bc857e1ef75681" + integrity sha512-+D4s2HCnxPd5PjjI0STKwncjXTUKKqm74MDMz9OPXavjsGmjkvwgLtA5yoxJUdmpj52+2u+RrXgPipahKczMKg== + node-fetch@2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5" @@ -3803,6 +3923,22 @@ node-notifier@^6.0.0: shellwords "^0.1.1" which "^1.3.1" +node-pre-gyp@0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz#c2fc383276b74c7ffa842925241553e8b40f1087" + integrity sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.3" + needle "^2.5.0" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4.4.2" + nodemon@^1.18.9: version "1.19.4" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.19.4.tgz#56db5c607408e0fdf8920d2b444819af1aae0971" @@ -3819,6 +3955,14 @@ nodemon@^1.18.9: undefsafe "^2.0.2" update-notifier "^2.5.0" +nopt@^4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" + integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== + dependencies: + abbrev "1" + osenv "^0.1.4" + nopt@~1.0.10: version "1.0.10" resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" @@ -3848,6 +3992,27 @@ normalize-path@^3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +npm-bundled@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" + integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== + dependencies: + npm-normalize-package-bin "^1.0.1" + +npm-normalize-package-bin@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" + integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== + +npm-packlist@^1.1.6: + version "1.4.8" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" + integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + npm-normalize-package-bin "^1.0.1" + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -3862,6 +4027,21 @@ npm-run-path@^4.0.0: dependencies: path-key "^3.0.0" +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + nwsapi@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" @@ -3872,6 +4052,11 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + object-copy@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" @@ -3981,11 +4166,24 @@ optionator@^0.8.1, optionator@^0.8.2: type-check "~0.3.2" word-wrap "~1.2.3" -os-tmpdir@~1.0.2: +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + +os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + p-each-series@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48" @@ -4360,7 +4558,7 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" -rc@^1.0.1, rc@^1.1.6: +rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== @@ -4411,7 +4609,7 @@ read-pkg@^5.2.0: parse-json "^5.0.0" type-fest "^0.6.0" -readable-stream@^2.0.2, readable-stream@^2.2.2, readable-stream@^2.3.5, readable-stream@^2.3.7: +readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.5, readable-stream@^2.3.7: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -4597,6 +4795,13 @@ rimraf@2.6.3: dependencies: glob "^7.1.3" +rimraf@^2.6.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -4658,6 +4863,11 @@ sane@^4.0.3: minimist "^1.1.1" walker "~1.0.5" +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + saxes@^3.1.9: version "3.1.11" resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b" @@ -4672,7 +4882,7 @@ semver-diff@^2.0.0: dependencies: semver "^5.0.3" -"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== @@ -4711,7 +4921,7 @@ serve-static@1.14.1: parseurl "~1.3.3" send "0.17.1" -set-blocking@^2.0.0: +set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= @@ -4949,7 +5159,16 @@ string-length@^3.1.0: astral-regex "^1.0.0" strip-ansi "^5.2.0" -string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -5005,6 +5224,13 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" @@ -5128,6 +5354,19 @@ table@^5.2.3: slice-ansi "^2.1.0" string-width "^3.0.0" +tar@^4.4.2: + version "4.4.13" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" + integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA== + dependencies: + chownr "^1.1.1" + fs-minipass "^1.2.5" + minipass "^2.8.6" + minizlib "^1.2.1" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.3" + term-size@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" @@ -5561,6 +5800,13 @@ which@^2.0.1, which@^2.0.2: dependencies: isexe "^2.0.0" +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + widest-line@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc" @@ -5680,6 +5926,11 @@ yallist@^2.1.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= +yallist@^3.0.0, yallist@^3.0.3: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + yargs-parser@^18.1.2: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" diff --git a/specification/user/api.json b/specification/user/api.json index e2efb1a..42fdcca 100644 --- a/specification/user/api.json +++ b/specification/user/api.json @@ -31,8 +31,8 @@ } ] }, - "user_form": { - "description": "The model used to create/update instances of users", + "user_create_form": { + "description": "The model used to create instances of users", "fields": [ { "name": "firstName", @@ -51,6 +51,32 @@ "type": "string" } ] + }, + "user_update_form": { + "description": "The model used to update instances of users", + "fields": [ + { + "name": "firstName", + "type": "string" + }, + { + "name": "lastName", + "type": "string" + }, + { + "name": "email", + "type": "string" + } + ] + }, + "user_password_form": { + "description": "The model used to update the 'password' field of a user", + "fields": [ + { + "name": "password", + "type": "string" + } + ] } }, "resources": { @@ -92,7 +118,7 @@ "method": "PUT", "description": "Create a user; methodName: createUser", "body": { - "type": "user_form" + "type": "user_create_form" }, "responses": { "201": { @@ -111,7 +137,7 @@ "description": "Update a user by id; methodName: updateUserById", "path": "/:id", "body": { - "type": "user_form" + "type": "user_update_form" }, "responses": { "200": { @@ -128,6 +154,25 @@ } } }, + { + "method": "PUT", + "description": "Update the password of a user; methodName: updateUserPassword", + "path": "/:id/password", + "body": { + "type": "user_password_form" + }, + "responses": { + "200": { + "type": "user" + }, + "404": { + "type": "unit" + }, + "400": { + "type": "unit" + } + } + }, { "method": "DELETE", "description": "Delete a user; methodName: deleteUser",