diff --git a/.eslintrc.json b/.eslintrc.json index 6f67564..c15f0a9 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,9 @@ { - "extends": "airbnb-base" -} \ No newline at end of file + "extends": "airbnb-base", + "rules": { + "max-len": ["error", { "code": 90, "comments": 120 }], + "no-param-reassign": ["error", { "props": false }], + "no-underscore-dangle": ["error", { "allow": ["_id"] }], + "no-unused-vars": ["error", {"args":"none"}] + } +} diff --git a/app/models/user.js b/app/models/user.js index f176304..a71ea49 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -10,7 +10,11 @@ const UserSchema = new Schema({ type: String, required: [true, "can't be blank"], }, email: { - type: String, lowercase: true, unique: true, required: [true, "can't be blank"], index: true, + type: String, + lowercase: true, + unique: true, + required: [true, "can't be blank"], + index: true, }, password: { type: String, required: [true, "can't be blank"], diff --git a/app/routes/auth/index.js b/app/routes/auth/index.js index a70e6fc..7055587 100644 --- a/app/routes/auth/index.js +++ b/app/routes/auth/index.js @@ -4,8 +4,8 @@ const register = require('./register'); const login = require('./login'); const reset = require('./reset'); -const RegisterValidation = require.main.require('./app/validation/register'); -const LoginValidation = require.main.require('./app/validation/login'); +const RegisterValidation = require.main.require('./app/validation/auth/register'); +const LoginValidation = require.main.require('./app/validation/auth/login'); /** * @api {post} /auth/register Register @@ -29,7 +29,7 @@ auth.post('/register', RegisterValidation, register); * @apiParam {String} email email of the user. * @apiParam {String} password password of the user. * - * @apiSuccess {string} access_token Access token. + * @apiSuccess {String} access_token Access token. */ auth.post('/login', LoginValidation, login); diff --git a/app/routes/auth/login.js b/app/routes/auth/login.js index bff4b7c..f250ce8 100644 --- a/app/routes/auth/login.js +++ b/app/routes/auth/login.js @@ -13,7 +13,10 @@ module.exports = (req, res, next) => { if (err) throw err; if (!user) { - return next({ status: 400, message: 'Authentication failed. User not found.' }); + return next({ + status: 400, + message: 'Authentication failed. User not found.', + }); } // check if password matches @@ -25,7 +28,11 @@ module.exports = (req, res, next) => { email: user.email, }; - const token = jwt.sign({ user: dataUser }, secret, { expiresIn: '12h' }); + const token = jwt.sign({ + user: dataUser, + }, secret, { + expiresIn: '3h', + }); // return the information including token as JSON return res.json({ @@ -40,7 +47,10 @@ module.exports = (req, res, next) => { }); } - return next({ status: 401, message: 'Authentication failed. Wrong password.' }); + return next({ + status: 401, + message: 'Authentication failed. Wrong password.', + }); }); }); }; diff --git a/app/routes/auth/register.js b/app/routes/auth/register.js index aaa4f02..bdfc616 100644 --- a/app/routes/auth/register.js +++ b/app/routes/auth/register.js @@ -13,14 +13,21 @@ module.exports = (req, res, next) => { password: passwordHash, }); - UserModel.countDocuments({ email: req.body.email }, (err, c) => { - if (c !== 0) { - return next({ status: 401, message: 'Email is already taken by another user.' }); + UserModel.countDocuments({ email: req.body.email }, (err, count) => { + if (count !== 0) { + return next({ + status: 401, + message: 'Email is already taken by another user.', + }); } return User.save((saveErr) => { if (saveErr) { - return next({ status: 500, message: 'Database error', error: [saveErr] }); + return next({ + status: 500, + message: 'Database error', + error: [saveErr], + }); } return res.status(201).json({ success: true, message: 'Success' }); diff --git a/app/routes/note/create.js b/app/routes/note/create.js index c7b331e..51127af 100644 --- a/app/routes/note/create.js +++ b/app/routes/note/create.js @@ -6,7 +6,11 @@ module.exports = (req, res, next) => { const { user } = jwt.decode(req.headers.authorization); - const Note = new NoteModel({ title: req.body.title, text: req.body.text, user: user.id }); + const Note = new NoteModel({ + title: req.body.title, + text: req.body.text, + user: user.id, + }); Note.save((err) => { if (err) { diff --git a/app/routes/note/index.js b/app/routes/note/index.js index d40119c..0c95137 100644 --- a/app/routes/note/index.js +++ b/app/routes/note/index.js @@ -7,7 +7,7 @@ const remove = require('./delete'); const CreateValidation = require.main.require('./app/validation/note/create'); const UpdateValidation = require.main.require('./app/validation/note/update'); -const Authentication = require.main.require('./app/validation/auth'); +const Authentication = require.main.require('./app/validation/auth/auth'); /** * @api {get} /note/:id Get note @@ -16,8 +16,8 @@ const Authentication = require.main.require('./app/validation/auth'); * * @apiParam {String} id Note unique ID. * - * @apiSuccess {string} title Title of the note. - * @apiSuccess {string} text Text of the note. + * @apiSuccess {String} title Title of the note. + * @apiSuccess {String} text Text of the note. */ note.get('/:id', Authentication, single); @@ -30,8 +30,8 @@ note.get('/:id', Authentication, single); * "Authorization": "" * } * - * @apiSuccess {string} title Title of the note. - * @apiSuccess {string} text Text of the note. + * @apiSuccess {String} title Title of the note. + * @apiSuccess {String} text Text of the note. */ note.post('/', Authentication, CreateValidation, create); @@ -42,8 +42,8 @@ note.post('/', Authentication, CreateValidation, create); * * @apiParam {String} id Note unique ID. * - * @apiSuccess {string} title Title of the note. - * @apiSuccess {string} text Text of the note. + * @apiSuccess {String} title Title of the note. + * @apiSuccess {String} text Text of the note. */ note.put('/:id', Authentication, UpdateValidation, update); @@ -58,7 +58,7 @@ note.put('/:id', Authentication, UpdateValidation, update); * "message": "Note successfully deleted." * } * @apiErrorExample {json} Error-Response: - * HTTP/1.1 403 Not Found + * HTTP/1.1 403 Forbidden * { * "success": false, * "message": "Access forbidden.", diff --git a/app/routes/user/delete.js b/app/routes/user/delete.js index 0e97c8b..8eeefeb 100644 --- a/app/routes/user/delete.js +++ b/app/routes/user/delete.js @@ -1,5 +1,42 @@ -module.exports = (req, res) => { - const user = {}; +const mongoose = require('mongoose'); +const jwt = require('jsonwebtoken'); +const bcrypt = require('bcrypt-nodejs'); - res.status(200).json({ user }); +module.exports = (req, res, next) => { + const UserModel = mongoose.model('User'); + + const { user } = jwt.decode(req.headers.authorization); + + return UserModel.findOne({ _id: user.id }) + .lean() + .exec() + .then((User) => { + if (User === null) { + return next({ status: 404, message: 'User does not exists.' }); + } + + return bcrypt.compare( + req.body.password, + User.password, + (error, result) => { + if (!result || error) { + return next({ + status: 401, + message: 'Authentication failed. Wrong password.', + }); + } + + return UserModel.deleteOne({ _id: User._id }, (err) => { + if (err) { + return next({ status: 500 }); + } + + return res.status(204).json({ + success: true, + message: 'Account deleted.', + }); + }); + }, + ); + }); }; diff --git a/app/routes/user/index.js b/app/routes/user/index.js index 42ffe60..e496cdd 100644 --- a/app/routes/user/index.js +++ b/app/routes/user/index.js @@ -5,13 +5,16 @@ const update = require('./update'); const remove = require('./delete'); const notes = require('./notes'); -const Authentication = require.main.require('./app/validation/auth'); +const Authentication = require.main.require('./app/validation/auth/auth'); +const UpdateValidation = require.main.require('./app/validation/user/update'); +const DeleteValidation = require.main.require('./app/validation/user/delete'); /** * @api {get} /user/me Get account information * @apiName GetUser * @apiGroup User * + * @apiSuccess {String} id Unique ID of the User. * @apiSuccess {String} firstname Firstname of the User. * @apiSuccess {String} lastname Lastname of the User. * @apiSuccess {String} email Email of the User. @@ -22,21 +25,29 @@ user.get('/me', Authentication, profile); * @api {put} /user/me Update account information * @apiName UpdateUser * @apiGroup User + * @apiDescription Send only password and new_password to change the password. Otherwise they will be ignored. * - * @apiParam {String} Firstname new firstname. - * @apiParam {String} Lastname new lastname. - * @apiParam {String} Email new email address. + * @apiParam {String} firstname New firstname. (optional) + * @apiParam {String} lastname New lastname. (optional) + * @apiParam {String} email New email address. (optional) + * @apiParam {String} password Actual password. (optional) + * @apiParam {String} new_password New password (only if you passed password parameter). * - * @apiSuccess {Object} user User object. + * @apiSuccess {String} id Unique ID of the User. + * @apiSuccess {String} firstname Firstname of the User. + * @apiSuccess {String} lastname Lastname of the User. + * @apiSuccess {String} email Email of the User. */ -user.put('/me', Authentication, update); +user.put('/me', Authentication, UpdateValidation, update); /** * @api {delete} /user/me Delete account * @apiName DeleteUser * @apiGroup User + * + * @apiParam {String} password Account password. */ -user.delete('/me', Authentication, remove); +user.delete('/me', Authentication, DeleteValidation, remove); /** * @api {get} /user/me/notes Get all notes diff --git a/app/routes/user/profile.js b/app/routes/user/profile.js index 66c0e4b..357e1d6 100644 --- a/app/routes/user/profile.js +++ b/app/routes/user/profile.js @@ -9,12 +9,6 @@ module.exports = (req, res, next) => { return UserModel.findById(user.id, 'id firstname lastname email') .lean() .exec() - .then((result) => { - if (result === null) { - return next({ status: 401, message: 'User does not exists.' }); - } - - return res.status(200).json(result); - }) + .then(result => res.status(200).json(result)) .catch(() => next({ status: 401, message: 'User does not exists.' })); }; diff --git a/app/routes/user/update.js b/app/routes/user/update.js index 0e97c8b..e53eae2 100644 --- a/app/routes/user/update.js +++ b/app/routes/user/update.js @@ -1,5 +1,56 @@ -module.exports = (req, res) => { - const user = {}; +const mongoose = require('mongoose'); +const jwt = require('jsonwebtoken'); +const bcrypt = require('bcrypt-nodejs'); - res.status(200).json({ user }); +module.exports = (req, res, next) => { + const UserModel = mongoose.model('User'); + + const { user } = jwt.decode(req.headers.authorization); + + return UserModel.findOne({ _id: user.id }, (err, userObj) => { + if (!userObj) { + return next({ status: 401, message: 'User does not exists.' }); + } + + if (req.body.password && req.body.new_password) { + return bcrypt.compare(req.body.password, userObj.password, (error, result) => { + if (!result || error) { + return next( + { + status: 401, + message: 'Authentication failed. Wrong password.', + }, + ); + } + + return bcrypt.hash(req.body.new_password, null, null, (hashErr, hash) => { + userObj.password = hash; + + const response = { + _id: userObj._id, + firstname: userObj.firstname, + lastname: userObj.lastname, + email: userObj.email, + createdAt: userObj.createdAt, + }; + + return userObj.save(() => res.status(200).json(response)); + }); + }); + } + + userObj.firstname = req.body.firstname || userObj.firstname; + userObj.lastname = req.body.lastname || userObj.lastname; + userObj.email = req.body.email || userObj.email; + + const response = { + _id: userObj._id, + firstname: userObj.firstname, + lastname: userObj.lastname, + email: userObj.email, + createdAt: userObj.createdAt, + }; + + return userObj.save(() => res.status(200).json(response)); + }); }; diff --git a/app/validation/auth.js b/app/validation/auth/auth.js similarity index 58% rename from app/validation/auth.js rename to app/validation/auth/auth.js index 924c154..1ff58b4 100644 --- a/app/validation/auth.js +++ b/app/validation/auth/auth.js @@ -1,9 +1,12 @@ const Joi = require('joi'); const jwt = require('jsonwebtoken'); +const mongoose = require('mongoose'); const secret = require.main.require('./config/secret'); module.exports = (req, res, next) => { + const UserModel = mongoose.model('User'); + const schema = Joi.object().keys({ access_token: Joi.string().required(), }); @@ -21,7 +24,21 @@ module.exports = (req, res, next) => { return next({ status: 401, message: 'Token error.', error: [err] }); } - return next(); + return UserModel.countDocuments( + { + _id: decoded.user.id, + }, (QueryError, count) => { + if (count !== 1) { + return next({ + status: 401, + message: 'Your session is invalid. Please try sign in again.', + error: [], + }); + } + + return next(); + }, + ); }); }); }; diff --git a/app/validation/login.js b/app/validation/auth/login.js similarity index 75% rename from app/validation/login.js rename to app/validation/auth/login.js index 0147335..b0850a7 100644 --- a/app/validation/login.js +++ b/app/validation/auth/login.js @@ -12,7 +12,11 @@ module.exports = (req, res, next) => { }, schema, (validateErr) => { if (validateErr) { - return next({ status: 400, message: 'Form is invalid.', error: validateErr.details }); + return next({ + status: 400, + message: 'Form is invalid.', + error: validateErr.details, + }); } return next(); diff --git a/app/validation/register.js b/app/validation/auth/register.js similarity index 81% rename from app/validation/register.js rename to app/validation/auth/register.js index 9db7087..c148ad6 100644 --- a/app/validation/register.js +++ b/app/validation/auth/register.js @@ -16,7 +16,11 @@ module.exports = (req, res, next) => { }, schema, (validateErr) => { if (validateErr) { - return next({ status: 400, message: 'Form is invalid.', error: validateErr.details }); + return next({ + status: 400, + message: 'Form is invalid.', + error: validateErr.details, + }); } return next(); diff --git a/app/validation/note/create.js b/app/validation/note/create.js index 2610694..09be690 100644 --- a/app/validation/note/create.js +++ b/app/validation/note/create.js @@ -12,7 +12,11 @@ module.exports = (req, res, next) => { }, schema, (validateErr) => { if (validateErr) { - return next({ status: 400, message: 'Form is invalid.', error: validateErr.details }); + return next({ + status: 400, + message: 'Form is invalid.', + error: validateErr.details, + }); } return next(); diff --git a/app/validation/note/update.js b/app/validation/note/update.js index 33d584c..385d731 100644 --- a/app/validation/note/update.js +++ b/app/validation/note/update.js @@ -12,7 +12,11 @@ module.exports = (req, res, next) => { }, schema, (validateErr) => { if (validateErr) { - return next({ status: 400, message: 'Form is invalid.', error: validateErr.details }); + return next({ + status: 400, + message: 'Form is invalid.', + error: validateErr.details, + }); } return next(); diff --git a/app/validation/user/delete.js b/app/validation/user/delete.js new file mode 100644 index 0000000..58becc1 --- /dev/null +++ b/app/validation/user/delete.js @@ -0,0 +1,22 @@ +const Joi = require('joi'); + +module.exports = (req, res, next) => { + const schema = Joi.object().keys({ + password: Joi.string().required(), + }); + + Joi.validate({ + password: req.body.password, + }, + schema, (validateErr) => { + if (validateErr) { + return next({ + status: 400, + message: 'Form is invalid.', + error: validateErr.details, + }); + } + + return next(); + }); +}; diff --git a/app/validation/user/update.js b/app/validation/user/update.js new file mode 100644 index 0000000..7b1c56c --- /dev/null +++ b/app/validation/user/update.js @@ -0,0 +1,33 @@ +const Joi = require('joi'); + +module.exports = (req, res, next) => { + const schema = Joi.object().keys({ + firstname: Joi.string().min(2), + lastname: Joi.string().min(2), + email: Joi.string().email({ minDomainAtoms: 2 }), + password: Joi.string(), + new_password: Joi.string(), + }).with('password', 'new_password') + .without('firstname', 'password') + .without('lastname', 'password') + .without('email', 'password'); + + Joi.validate({ + firstname: req.body.firstname, + lastname: req.body.lastname, + email: req.body.email, + password: req.body.password, + new_password: req.body.new_password, + }, + schema, (validateErr) => { + if (validateErr) { + return next({ + status: 400, + message: 'Form is invalid.', + error: validateErr.details, + }); + } + + return next(); + }); +}; diff --git a/docs/api_data.js b/docs/api_data.js index e884f89..1d1fac8 100644 --- a/docs/api_data.js +++ b/docs/api_data.js @@ -12,8 +12,8 @@ define({ "api": [ "group": "Parameter", "type": "String", "optional": false, - "field": "username", - "description": "

username of the user.

" + "field": "email", + "description": "

email of the user.

" }, { "group": "Parameter", @@ -30,7 +30,7 @@ define({ "api": [ "Success 200": [ { "group": "Success 200", - "type": "string", + "type": "String", "optional": false, "field": "access_token", "description": "

Access token.

" @@ -69,8 +69,8 @@ define({ "api": [ "group": "Parameter", "type": "String", "optional": false, - "field": "username", - "description": "

username of the user.

" + "field": "email", + "description": "

email of the user.

" }, { "group": "Parameter", @@ -99,6 +99,36 @@ define({ "api": [ "filename": "app/routes/auth/index.js", "groupTitle": "Auth" }, + { + "type": "post", + "url": "/auth/reset-password", + "title": "Reset password", + "name": "ResetPass", + "group": "Auth", + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "email", + "description": "

email of the user.

" + }, + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "password", + "description": "

password of the user.

" + } + ] + } + }, + "version": "0.0.0", + "filename": "app/routes/auth/index.js", + "groupTitle": "Auth" + }, { "type": "post", "url": "/note", @@ -119,14 +149,14 @@ define({ "api": [ "Success 200": [ { "group": "Success 200", - "type": "string", + "type": "String", "optional": false, "field": "title", "description": "

Title of the note.

" }, { "group": "Success 200", - "type": "string", + "type": "String", "optional": false, "field": "text", "description": "

Text of the note.

" @@ -157,7 +187,7 @@ define({ "api": [ "examples": [ { "title": "Error-Response:", - "content": "HTTP/1.1 403 Not Found\n{\n \"success\": false,\n \"message\": \"Access forbidden.\",\n \"errors\": []\n}", + "content": "HTTP/1.1 403 Forbidden\n{\n \"success\": false,\n \"message\": \"Access forbidden.\",\n \"errors\": []\n}", "type": "json" } ] @@ -203,14 +233,14 @@ define({ "api": [ "Success 200": [ { "group": "Success 200", - "type": "string", + "type": "String", "optional": false, "field": "title", "description": "

Title of the note.

" }, { "group": "Success 200", - "type": "string", + "type": "String", "optional": false, "field": "text", "description": "

Text of the note.

" @@ -246,14 +276,14 @@ define({ "api": [ "Success 200": [ { "group": "Success 200", - "type": "string", + "type": "String", "optional": false, "field": "title", "description": "

Title of the note.

" }, { "group": "Success 200", - "type": "string", + "type": "String", "optional": false, "field": "text", "description": "

Text of the note.

" @@ -271,6 +301,19 @@ define({ "api": [ "title": "Delete account", "name": "DeleteUser", "group": "User", + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "password", + "description": "

Account password.

" + } + ] + } + }, "version": "0.0.0", "filename": "app/routes/user/index.js", "groupTitle": "User" @@ -278,12 +321,19 @@ define({ "api": [ { "type": "get", "url": "/user/me", - "title": "Get user information", + "title": "Get account information", "name": "GetUser", "group": "User", "success": { "fields": { "Success 200": [ + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "id", + "description": "

Unique ID of the User.

" + }, { "group": "Success 200", "type": "String", @@ -318,15 +368,78 @@ define({ "api": [ "title": "Update account information", "name": "UpdateUser", "group": "User", + "description": "

Send only password and new_password to change the password. Otherwise they will be ignored.

", + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "firstname", + "description": "

New firstname. (optional)

" + }, + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "lastname", + "description": "

New lastname. (optional)

" + }, + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "email", + "description": "

New email address. (optional)

" + }, + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "password", + "description": "

Actual password. (optional)

" + }, + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "new_password", + "description": "

New password (only if you passed password parameter).

" + } + ] + } + }, "success": { "fields": { "Success 200": [ { "group": "Success 200", - "type": "Object", + "type": "String", "optional": false, - "field": "user", - "description": "

User object.

" + "field": "id", + "description": "

Unique ID of the User.

" + }, + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "firstname", + "description": "

Firstname of the User.

" + }, + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "lastname", + "description": "

Lastname of the User.

" + }, + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "email", + "description": "

Email of the User.

" } ] } diff --git a/docs/api_data.json b/docs/api_data.json index 16d22ec..e9c24cc 100644 --- a/docs/api_data.json +++ b/docs/api_data.json @@ -12,8 +12,8 @@ "group": "Parameter", "type": "String", "optional": false, - "field": "username", - "description": "

username of the user.

" + "field": "email", + "description": "

email of the user.

" }, { "group": "Parameter", @@ -30,7 +30,7 @@ "Success 200": [ { "group": "Success 200", - "type": "string", + "type": "String", "optional": false, "field": "access_token", "description": "

Access token.

" @@ -69,8 +69,8 @@ "group": "Parameter", "type": "String", "optional": false, - "field": "username", - "description": "

username of the user.

" + "field": "email", + "description": "

email of the user.

" }, { "group": "Parameter", @@ -99,6 +99,36 @@ "filename": "app/routes/auth/index.js", "groupTitle": "Auth" }, + { + "type": "post", + "url": "/auth/reset-password", + "title": "Reset password", + "name": "ResetPass", + "group": "Auth", + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "email", + "description": "

email of the user.

" + }, + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "password", + "description": "

password of the user.

" + } + ] + } + }, + "version": "0.0.0", + "filename": "app/routes/auth/index.js", + "groupTitle": "Auth" + }, { "type": "post", "url": "/note", @@ -119,14 +149,14 @@ "Success 200": [ { "group": "Success 200", - "type": "string", + "type": "String", "optional": false, "field": "title", "description": "

Title of the note.

" }, { "group": "Success 200", - "type": "string", + "type": "String", "optional": false, "field": "text", "description": "

Text of the note.

" @@ -157,7 +187,7 @@ "examples": [ { "title": "Error-Response:", - "content": "HTTP/1.1 403 Not Found\n{\n \"success\": false,\n \"message\": \"Access forbidden.\",\n \"errors\": []\n}", + "content": "HTTP/1.1 403 Forbidden\n{\n \"success\": false,\n \"message\": \"Access forbidden.\",\n \"errors\": []\n}", "type": "json" } ] @@ -203,14 +233,14 @@ "Success 200": [ { "group": "Success 200", - "type": "string", + "type": "String", "optional": false, "field": "title", "description": "

Title of the note.

" }, { "group": "Success 200", - "type": "string", + "type": "String", "optional": false, "field": "text", "description": "

Text of the note.

" @@ -246,14 +276,14 @@ "Success 200": [ { "group": "Success 200", - "type": "string", + "type": "String", "optional": false, "field": "title", "description": "

Title of the note.

" }, { "group": "Success 200", - "type": "string", + "type": "String", "optional": false, "field": "text", "description": "

Text of the note.

" @@ -271,6 +301,19 @@ "title": "Delete account", "name": "DeleteUser", "group": "User", + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "password", + "description": "

Account password.

" + } + ] + } + }, "version": "0.0.0", "filename": "app/routes/user/index.js", "groupTitle": "User" @@ -278,12 +321,19 @@ { "type": "get", "url": "/user/me", - "title": "Get user information", + "title": "Get account information", "name": "GetUser", "group": "User", "success": { "fields": { "Success 200": [ + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "id", + "description": "

Unique ID of the User.

" + }, { "group": "Success 200", "type": "String", @@ -318,15 +368,78 @@ "title": "Update account information", "name": "UpdateUser", "group": "User", + "description": "

Send only password and new_password to change the password. Otherwise they will be ignored.

", + "parameter": { + "fields": { + "Parameter": [ + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "firstname", + "description": "

New firstname. (optional)

" + }, + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "lastname", + "description": "

New lastname. (optional)

" + }, + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "email", + "description": "

New email address. (optional)

" + }, + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "password", + "description": "

Actual password. (optional)

" + }, + { + "group": "Parameter", + "type": "String", + "optional": false, + "field": "new_password", + "description": "

New password (only if you passed password parameter).

" + } + ] + } + }, "success": { "fields": { "Success 200": [ { "group": "Success 200", - "type": "Object", + "type": "String", "optional": false, - "field": "user", - "description": "

User object.

" + "field": "id", + "description": "

Unique ID of the User.

" + }, + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "firstname", + "description": "

Firstname of the User.

" + }, + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "lastname", + "description": "

Lastname of the User.

" + }, + { + "group": "Success 200", + "type": "String", + "optional": false, + "field": "email", + "description": "

Email of the User.

" } ] } diff --git a/docs/api_project.js b/docs/api_project.js index c53faf1..07c24d7 100644 --- a/docs/api_project.js +++ b/docs/api_project.js @@ -8,7 +8,7 @@ define({ "apidoc": "0.3.0", "generator": { "name": "apidoc", - "time": "2018-11-15T18:00:50.902Z", + "time": "2018-11-17T00:00:47.768Z", "url": "http://apidocjs.com", "version": "0.17.7" } diff --git a/docs/api_project.json b/docs/api_project.json index 4af392b..8f24bc2 100644 --- a/docs/api_project.json +++ b/docs/api_project.json @@ -8,7 +8,7 @@ "apidoc": "0.3.0", "generator": { "name": "apidoc", - "time": "2018-11-15T18:00:50.902Z", + "time": "2018-11-17T00:00:47.768Z", "url": "http://apidocjs.com", "version": "0.17.7" } diff --git a/index.js b/index.js index 9ce0d18..57f04cf 100644 --- a/index.js +++ b/index.js @@ -10,7 +10,11 @@ const port = process.env.PORT || 8080; // set our port app.use('/', routes); app.use((err, req, res, next) => { - res.status(err.status || 400).json({ success: false, message: err.message || 'An error occured.', errors: err.error || [] }); + res.status(err.status || 400).json({ + success: false, + message: err.message || 'An error occured.', + errors: err.error || [], + }); }); app.use((req, res) => { diff --git a/todo.md b/todo.md index b5edded..21ff1b2 100644 --- a/todo.md +++ b/todo.md @@ -2,11 +2,49 @@ ### Routes +- PUT /user/me (tous les champs sauf password) (200 ok avec en body le user après édition) - PUT /user/me (si tu vois password et new password, ou mets une autre route si tu veux) (200 ok avec le user après édition mais sans mot de passe bien sûr) -- DELETE /users/me (supprimer le profil) (204 no content, car le user est supprimé et tu ne me renvoi rien) - POST /auth/rester-password (email) (optionnel si tu te sens pas chaud) (200 ok) ### Other -- eslint test -- build test +- ESlint test + +## Specs + +**Pourvoir s'inscrire, se connecter, réinitialiser mot de passe (non connecté) :** + +- POST /auth/register (firstname, lastname, email, password) + (201 created) +- POST /auth/login (email, password) (200 ok avec en body le user et en header le token d'authentification) +- POST /auth/rester-password (email) (optionnel si tu te sens pas chaud) + (200 ok) + +**Pouvoir voir / modifier / supprimer son profil (connecté):** + +- PUT /user/me (tous les champs sauf password) (200 ok avec en body le user après édition) +- PUT /user/me (si tu vois password et new password, ou mets une autre route si tu veux) (200 ok avec le user après édition mais sans mot de passe bien sûr) +- DELETE /users/me (supprimer le profil) (204 no content, car le user est supprimé et tu ne me renvoi rien) +- GET /users/me (renvoi le user) (200 ok avec en body le user) pouvoir gérer les notes (connecté) + +**Notes:** + +- POST /notes (title optionnel, text optionnel, lié a user connecté) (201 creatred) +- PUT /notes/id (title optionnel, text optionnel) (200 ok avec en body la noté modifiée) +- DELETE /notes/id (supprimer la note) (204 no content) +- GET /user/me/ notes (toutes notes du user connecté) +- GET /notes/id (get la note de cet id si elle appartient au user connecté) + +**Finito ! C'est un crud avec un minimum de vérif qui sont :** + +- Est-ce que le user est connecté ou non +- Est-ce que le user est propriétaire de la ressource qu'il modifie ou supprime + +C'est tout ce qu'il y a besoin de vérifier. + +**Pour tout ce qui est erreur il n'y a que 4 règles à respecter :** + +- Pas le droit car pas connecté : 401 +- Pas le droit car connecté mais ne t'appartient pas : 403 +- Ressource n'existe pas : 404 +- Erreur de validation (si tu veux en faire) : 400 bad request