Merge pull request #9 from sundowndev/develop

v1.0.0 release
master
Raphael Cerveaux 2018-11-17 12:12:35 +01:00 committed by GitHub
commit 03701bc0de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 565 additions and 85 deletions

View File

@ -1,3 +1,9 @@
{ {
"extends": "airbnb-base" "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"}]
}
} }

View File

@ -10,7 +10,11 @@ const UserSchema = new Schema({
type: String, required: [true, "can't be blank"], type: String, required: [true, "can't be blank"],
}, },
email: { 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: { password: {
type: String, required: [true, "can't be blank"], type: String, required: [true, "can't be blank"],

View File

@ -4,8 +4,8 @@ const register = require('./register');
const login = require('./login'); const login = require('./login');
const reset = require('./reset'); const reset = require('./reset');
const RegisterValidation = require.main.require('./app/validation/register'); const RegisterValidation = require.main.require('./app/validation/auth/register');
const LoginValidation = require.main.require('./app/validation/login'); const LoginValidation = require.main.require('./app/validation/auth/login');
/** /**
* @api {post} /auth/register Register * @api {post} /auth/register Register
@ -29,7 +29,7 @@ auth.post('/register', RegisterValidation, register);
* @apiParam {String} email email of the user. * @apiParam {String} email email of the user.
* @apiParam {String} password password 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); auth.post('/login', LoginValidation, login);

View File

@ -13,7 +13,10 @@ module.exports = (req, res, next) => {
if (err) throw err; if (err) throw err;
if (!user) { 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 // check if password matches
@ -25,7 +28,11 @@ module.exports = (req, res, next) => {
email: user.email, 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 the information including token as JSON
return res.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.',
});
}); });
}); });
}; };

View File

@ -13,14 +13,21 @@ module.exports = (req, res, next) => {
password: passwordHash, password: passwordHash,
}); });
UserModel.countDocuments({ email: req.body.email }, (err, c) => { UserModel.countDocuments({ email: req.body.email }, (err, count) => {
if (c !== 0) { if (count !== 0) {
return next({ status: 401, message: 'Email is already taken by another user.' }); return next({
status: 401,
message: 'Email is already taken by another user.',
});
} }
return User.save((saveErr) => { return User.save((saveErr) => {
if (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' }); return res.status(201).json({ success: true, message: 'Success' });

View File

@ -6,7 +6,11 @@ module.exports = (req, res, next) => {
const { user } = jwt.decode(req.headers.authorization); 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) => { Note.save((err) => {
if (err) { if (err) {

View File

@ -7,7 +7,7 @@ const remove = require('./delete');
const CreateValidation = require.main.require('./app/validation/note/create'); const CreateValidation = require.main.require('./app/validation/note/create');
const UpdateValidation = require.main.require('./app/validation/note/update'); 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 * @api {get} /note/:id Get note
@ -16,8 +16,8 @@ const Authentication = require.main.require('./app/validation/auth');
* *
* @apiParam {String} id Note unique ID. * @apiParam {String} id Note unique ID.
* *
* @apiSuccess {string} title Title of the note. * @apiSuccess {String} title Title of the note.
* @apiSuccess {string} text Text of the note. * @apiSuccess {String} text Text of the note.
*/ */
note.get('/:id', Authentication, single); note.get('/:id', Authentication, single);
@ -30,8 +30,8 @@ note.get('/:id', Authentication, single);
* "Authorization": "<Access_Token>" * "Authorization": "<Access_Token>"
* } * }
* *
* @apiSuccess {string} title Title of the note. * @apiSuccess {String} title Title of the note.
* @apiSuccess {string} text Text of the note. * @apiSuccess {String} text Text of the note.
*/ */
note.post('/', Authentication, CreateValidation, create); note.post('/', Authentication, CreateValidation, create);
@ -42,8 +42,8 @@ note.post('/', Authentication, CreateValidation, create);
* *
* @apiParam {String} id Note unique ID. * @apiParam {String} id Note unique ID.
* *
* @apiSuccess {string} title Title of the note. * @apiSuccess {String} title Title of the note.
* @apiSuccess {string} text Text of the note. * @apiSuccess {String} text Text of the note.
*/ */
note.put('/:id', Authentication, UpdateValidation, update); note.put('/:id', Authentication, UpdateValidation, update);
@ -58,7 +58,7 @@ note.put('/:id', Authentication, UpdateValidation, update);
* "message": "Note successfully deleted." * "message": "Note successfully deleted."
* } * }
* @apiErrorExample {json} Error-Response: * @apiErrorExample {json} Error-Response:
* HTTP/1.1 403 Not Found * HTTP/1.1 403 Forbidden
* { * {
* "success": false, * "success": false,
* "message": "Access forbidden.", * "message": "Access forbidden.",

View File

@ -1,5 +1,42 @@
module.exports = (req, res) => { const mongoose = require('mongoose');
const user = {}; 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.',
});
});
},
);
});
}; };

View File

@ -5,13 +5,16 @@ const update = require('./update');
const remove = require('./delete'); const remove = require('./delete');
const notes = require('./notes'); 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 * @api {get} /user/me Get account information
* @apiName GetUser * @apiName GetUser
* @apiGroup User * @apiGroup User
* *
* @apiSuccess {String} id Unique ID of the User.
* @apiSuccess {String} firstname Firstname of the User. * @apiSuccess {String} firstname Firstname of the User.
* @apiSuccess {String} lastname Lastname of the User. * @apiSuccess {String} lastname Lastname of the User.
* @apiSuccess {String} email Email 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 * @api {put} /user/me Update account information
* @apiName UpdateUser * @apiName UpdateUser
* @apiGroup User * @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} firstname New firstname. (optional)
* @apiParam {String} Lastname new lastname. * @apiParam {String} lastname New lastname. (optional)
* @apiParam {String} Email new email address. * @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 * @api {delete} /user/me Delete account
* @apiName DeleteUser * @apiName DeleteUser
* @apiGroup User * @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 * @api {get} /user/me/notes Get all notes

View File

@ -9,12 +9,6 @@ module.exports = (req, res, next) => {
return UserModel.findById(user.id, 'id firstname lastname email') return UserModel.findById(user.id, 'id firstname lastname email')
.lean() .lean()
.exec() .exec()
.then((result) => { .then(result => res.status(200).json(result))
if (result === null) {
return next({ status: 401, message: 'User does not exists.' });
}
return res.status(200).json(result);
})
.catch(() => next({ status: 401, message: 'User does not exists.' })); .catch(() => next({ status: 401, message: 'User does not exists.' }));
}; };

View File

@ -1,5 +1,56 @@
module.exports = (req, res) => { const mongoose = require('mongoose');
const user = {}; 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));
});
}; };

View File

@ -1,9 +1,12 @@
const Joi = require('joi'); const Joi = require('joi');
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
const mongoose = require('mongoose');
const secret = require.main.require('./config/secret'); const secret = require.main.require('./config/secret');
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
const UserModel = mongoose.model('User');
const schema = Joi.object().keys({ const schema = Joi.object().keys({
access_token: Joi.string().required(), 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({ 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();
},
);
}); });
}); });
}; };

View File

@ -12,7 +12,11 @@ module.exports = (req, res, next) => {
}, },
schema, (validateErr) => { schema, (validateErr) => {
if (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(); return next();

View File

@ -16,7 +16,11 @@ module.exports = (req, res, next) => {
}, },
schema, (validateErr) => { schema, (validateErr) => {
if (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(); return next();

View File

@ -12,7 +12,11 @@ module.exports = (req, res, next) => {
}, },
schema, (validateErr) => { schema, (validateErr) => {
if (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(); return next();

View File

@ -12,7 +12,11 @@ module.exports = (req, res, next) => {
}, },
schema, (validateErr) => { schema, (validateErr) => {
if (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(); return next();

View File

@ -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();
});
};

View File

@ -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();
});
};

View File

@ -12,8 +12,8 @@ define({ "api": [
"group": "Parameter", "group": "Parameter",
"type": "String", "type": "String",
"optional": false, "optional": false,
"field": "username", "field": "email",
"description": "<p>username of the user.</p>" "description": "<p>email of the user.</p>"
}, },
{ {
"group": "Parameter", "group": "Parameter",
@ -30,7 +30,7 @@ define({ "api": [
"Success 200": [ "Success 200": [
{ {
"group": "Success 200", "group": "Success 200",
"type": "string", "type": "String",
"optional": false, "optional": false,
"field": "access_token", "field": "access_token",
"description": "<p>Access token.</p>" "description": "<p>Access token.</p>"
@ -69,8 +69,8 @@ define({ "api": [
"group": "Parameter", "group": "Parameter",
"type": "String", "type": "String",
"optional": false, "optional": false,
"field": "username", "field": "email",
"description": "<p>username of the user.</p>" "description": "<p>email of the user.</p>"
}, },
{ {
"group": "Parameter", "group": "Parameter",
@ -99,6 +99,36 @@ define({ "api": [
"filename": "app/routes/auth/index.js", "filename": "app/routes/auth/index.js",
"groupTitle": "Auth" "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": "<p>email of the user.</p>"
},
{
"group": "Parameter",
"type": "String",
"optional": false,
"field": "password",
"description": "<p>password of the user.</p>"
}
]
}
},
"version": "0.0.0",
"filename": "app/routes/auth/index.js",
"groupTitle": "Auth"
},
{ {
"type": "post", "type": "post",
"url": "/note", "url": "/note",
@ -119,14 +149,14 @@ define({ "api": [
"Success 200": [ "Success 200": [
{ {
"group": "Success 200", "group": "Success 200",
"type": "string", "type": "String",
"optional": false, "optional": false,
"field": "title", "field": "title",
"description": "<p>Title of the note.</p>" "description": "<p>Title of the note.</p>"
}, },
{ {
"group": "Success 200", "group": "Success 200",
"type": "string", "type": "String",
"optional": false, "optional": false,
"field": "text", "field": "text",
"description": "<p>Text of the note.</p>" "description": "<p>Text of the note.</p>"
@ -157,7 +187,7 @@ define({ "api": [
"examples": [ "examples": [
{ {
"title": "Error-Response:", "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" "type": "json"
} }
] ]
@ -203,14 +233,14 @@ define({ "api": [
"Success 200": [ "Success 200": [
{ {
"group": "Success 200", "group": "Success 200",
"type": "string", "type": "String",
"optional": false, "optional": false,
"field": "title", "field": "title",
"description": "<p>Title of the note.</p>" "description": "<p>Title of the note.</p>"
}, },
{ {
"group": "Success 200", "group": "Success 200",
"type": "string", "type": "String",
"optional": false, "optional": false,
"field": "text", "field": "text",
"description": "<p>Text of the note.</p>" "description": "<p>Text of the note.</p>"
@ -246,14 +276,14 @@ define({ "api": [
"Success 200": [ "Success 200": [
{ {
"group": "Success 200", "group": "Success 200",
"type": "string", "type": "String",
"optional": false, "optional": false,
"field": "title", "field": "title",
"description": "<p>Title of the note.</p>" "description": "<p>Title of the note.</p>"
}, },
{ {
"group": "Success 200", "group": "Success 200",
"type": "string", "type": "String",
"optional": false, "optional": false,
"field": "text", "field": "text",
"description": "<p>Text of the note.</p>" "description": "<p>Text of the note.</p>"
@ -271,6 +301,19 @@ define({ "api": [
"title": "Delete account", "title": "Delete account",
"name": "DeleteUser", "name": "DeleteUser",
"group": "User", "group": "User",
"parameter": {
"fields": {
"Parameter": [
{
"group": "Parameter",
"type": "String",
"optional": false,
"field": "password",
"description": "<p>Account password.</p>"
}
]
}
},
"version": "0.0.0", "version": "0.0.0",
"filename": "app/routes/user/index.js", "filename": "app/routes/user/index.js",
"groupTitle": "User" "groupTitle": "User"
@ -278,12 +321,19 @@ define({ "api": [
{ {
"type": "get", "type": "get",
"url": "/user/me", "url": "/user/me",
"title": "Get user information", "title": "Get account information",
"name": "GetUser", "name": "GetUser",
"group": "User", "group": "User",
"success": { "success": {
"fields": { "fields": {
"Success 200": [ "Success 200": [
{
"group": "Success 200",
"type": "String",
"optional": false,
"field": "id",
"description": "<p>Unique ID of the User.</p>"
},
{ {
"group": "Success 200", "group": "Success 200",
"type": "String", "type": "String",
@ -318,15 +368,78 @@ define({ "api": [
"title": "Update account information", "title": "Update account information",
"name": "UpdateUser", "name": "UpdateUser",
"group": "User", "group": "User",
"description": "<p>Send only password and new_password to change the password. Otherwise they will be ignored.</p>",
"parameter": {
"fields": {
"Parameter": [
{
"group": "Parameter",
"type": "String",
"optional": false,
"field": "firstname",
"description": "<p>New firstname. (optional)</p>"
},
{
"group": "Parameter",
"type": "String",
"optional": false,
"field": "lastname",
"description": "<p>New lastname. (optional)</p>"
},
{
"group": "Parameter",
"type": "String",
"optional": false,
"field": "email",
"description": "<p>New email address. (optional)</p>"
},
{
"group": "Parameter",
"type": "String",
"optional": false,
"field": "password",
"description": "<p>Actual password. (optional)</p>"
},
{
"group": "Parameter",
"type": "String",
"optional": false,
"field": "new_password",
"description": "<p>New password (only if you passed password parameter).</p>"
}
]
}
},
"success": { "success": {
"fields": { "fields": {
"Success 200": [ "Success 200": [
{ {
"group": "Success 200", "group": "Success 200",
"type": "Object", "type": "String",
"optional": false, "optional": false,
"field": "user", "field": "id",
"description": "<p>User object.</p>" "description": "<p>Unique ID of the User.</p>"
},
{
"group": "Success 200",
"type": "String",
"optional": false,
"field": "firstname",
"description": "<p>Firstname of the User.</p>"
},
{
"group": "Success 200",
"type": "String",
"optional": false,
"field": "lastname",
"description": "<p>Lastname of the User.</p>"
},
{
"group": "Success 200",
"type": "String",
"optional": false,
"field": "email",
"description": "<p>Email of the User.</p>"
} }
] ]
} }

View File

@ -12,8 +12,8 @@
"group": "Parameter", "group": "Parameter",
"type": "String", "type": "String",
"optional": false, "optional": false,
"field": "username", "field": "email",
"description": "<p>username of the user.</p>" "description": "<p>email of the user.</p>"
}, },
{ {
"group": "Parameter", "group": "Parameter",
@ -30,7 +30,7 @@
"Success 200": [ "Success 200": [
{ {
"group": "Success 200", "group": "Success 200",
"type": "string", "type": "String",
"optional": false, "optional": false,
"field": "access_token", "field": "access_token",
"description": "<p>Access token.</p>" "description": "<p>Access token.</p>"
@ -69,8 +69,8 @@
"group": "Parameter", "group": "Parameter",
"type": "String", "type": "String",
"optional": false, "optional": false,
"field": "username", "field": "email",
"description": "<p>username of the user.</p>" "description": "<p>email of the user.</p>"
}, },
{ {
"group": "Parameter", "group": "Parameter",
@ -99,6 +99,36 @@
"filename": "app/routes/auth/index.js", "filename": "app/routes/auth/index.js",
"groupTitle": "Auth" "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": "<p>email of the user.</p>"
},
{
"group": "Parameter",
"type": "String",
"optional": false,
"field": "password",
"description": "<p>password of the user.</p>"
}
]
}
},
"version": "0.0.0",
"filename": "app/routes/auth/index.js",
"groupTitle": "Auth"
},
{ {
"type": "post", "type": "post",
"url": "/note", "url": "/note",
@ -119,14 +149,14 @@
"Success 200": [ "Success 200": [
{ {
"group": "Success 200", "group": "Success 200",
"type": "string", "type": "String",
"optional": false, "optional": false,
"field": "title", "field": "title",
"description": "<p>Title of the note.</p>" "description": "<p>Title of the note.</p>"
}, },
{ {
"group": "Success 200", "group": "Success 200",
"type": "string", "type": "String",
"optional": false, "optional": false,
"field": "text", "field": "text",
"description": "<p>Text of the note.</p>" "description": "<p>Text of the note.</p>"
@ -157,7 +187,7 @@
"examples": [ "examples": [
{ {
"title": "Error-Response:", "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" "type": "json"
} }
] ]
@ -203,14 +233,14 @@
"Success 200": [ "Success 200": [
{ {
"group": "Success 200", "group": "Success 200",
"type": "string", "type": "String",
"optional": false, "optional": false,
"field": "title", "field": "title",
"description": "<p>Title of the note.</p>" "description": "<p>Title of the note.</p>"
}, },
{ {
"group": "Success 200", "group": "Success 200",
"type": "string", "type": "String",
"optional": false, "optional": false,
"field": "text", "field": "text",
"description": "<p>Text of the note.</p>" "description": "<p>Text of the note.</p>"
@ -246,14 +276,14 @@
"Success 200": [ "Success 200": [
{ {
"group": "Success 200", "group": "Success 200",
"type": "string", "type": "String",
"optional": false, "optional": false,
"field": "title", "field": "title",
"description": "<p>Title of the note.</p>" "description": "<p>Title of the note.</p>"
}, },
{ {
"group": "Success 200", "group": "Success 200",
"type": "string", "type": "String",
"optional": false, "optional": false,
"field": "text", "field": "text",
"description": "<p>Text of the note.</p>" "description": "<p>Text of the note.</p>"
@ -271,6 +301,19 @@
"title": "Delete account", "title": "Delete account",
"name": "DeleteUser", "name": "DeleteUser",
"group": "User", "group": "User",
"parameter": {
"fields": {
"Parameter": [
{
"group": "Parameter",
"type": "String",
"optional": false,
"field": "password",
"description": "<p>Account password.</p>"
}
]
}
},
"version": "0.0.0", "version": "0.0.0",
"filename": "app/routes/user/index.js", "filename": "app/routes/user/index.js",
"groupTitle": "User" "groupTitle": "User"
@ -278,12 +321,19 @@
{ {
"type": "get", "type": "get",
"url": "/user/me", "url": "/user/me",
"title": "Get user information", "title": "Get account information",
"name": "GetUser", "name": "GetUser",
"group": "User", "group": "User",
"success": { "success": {
"fields": { "fields": {
"Success 200": [ "Success 200": [
{
"group": "Success 200",
"type": "String",
"optional": false,
"field": "id",
"description": "<p>Unique ID of the User.</p>"
},
{ {
"group": "Success 200", "group": "Success 200",
"type": "String", "type": "String",
@ -318,15 +368,78 @@
"title": "Update account information", "title": "Update account information",
"name": "UpdateUser", "name": "UpdateUser",
"group": "User", "group": "User",
"description": "<p>Send only password and new_password to change the password. Otherwise they will be ignored.</p>",
"parameter": {
"fields": {
"Parameter": [
{
"group": "Parameter",
"type": "String",
"optional": false,
"field": "firstname",
"description": "<p>New firstname. (optional)</p>"
},
{
"group": "Parameter",
"type": "String",
"optional": false,
"field": "lastname",
"description": "<p>New lastname. (optional)</p>"
},
{
"group": "Parameter",
"type": "String",
"optional": false,
"field": "email",
"description": "<p>New email address. (optional)</p>"
},
{
"group": "Parameter",
"type": "String",
"optional": false,
"field": "password",
"description": "<p>Actual password. (optional)</p>"
},
{
"group": "Parameter",
"type": "String",
"optional": false,
"field": "new_password",
"description": "<p>New password (only if you passed password parameter).</p>"
}
]
}
},
"success": { "success": {
"fields": { "fields": {
"Success 200": [ "Success 200": [
{ {
"group": "Success 200", "group": "Success 200",
"type": "Object", "type": "String",
"optional": false, "optional": false,
"field": "user", "field": "id",
"description": "<p>User object.</p>" "description": "<p>Unique ID of the User.</p>"
},
{
"group": "Success 200",
"type": "String",
"optional": false,
"field": "firstname",
"description": "<p>Firstname of the User.</p>"
},
{
"group": "Success 200",
"type": "String",
"optional": false,
"field": "lastname",
"description": "<p>Lastname of the User.</p>"
},
{
"group": "Success 200",
"type": "String",
"optional": false,
"field": "email",
"description": "<p>Email of the User.</p>"
} }
] ]
} }

View File

@ -8,7 +8,7 @@ define({
"apidoc": "0.3.0", "apidoc": "0.3.0",
"generator": { "generator": {
"name": "apidoc", "name": "apidoc",
"time": "2018-11-15T18:00:50.902Z", "time": "2018-11-17T00:00:47.768Z",
"url": "http://apidocjs.com", "url": "http://apidocjs.com",
"version": "0.17.7" "version": "0.17.7"
} }

View File

@ -8,7 +8,7 @@
"apidoc": "0.3.0", "apidoc": "0.3.0",
"generator": { "generator": {
"name": "apidoc", "name": "apidoc",
"time": "2018-11-15T18:00:50.902Z", "time": "2018-11-17T00:00:47.768Z",
"url": "http://apidocjs.com", "url": "http://apidocjs.com",
"version": "0.17.7" "version": "0.17.7"
} }

View File

@ -10,7 +10,11 @@ const port = process.env.PORT || 8080; // set our port
app.use('/', routes); app.use('/', routes);
app.use((err, req, res, next) => { 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) => { app.use((req, res) => {

44
todo.md
View File

@ -2,11 +2,49 @@
### Routes ### 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) - 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) - POST /auth/rester-password (email) (optionnel si tu te sens pas chaud) (200 ok)
### Other ### Other
- eslint test - ESlint test
- build 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