Skip to content

Commit

Permalink
✨ Error creation (#7477)
Browse files Browse the repository at this point in the history
refs #7116, refs #2001

- Changes the way Ghost errors are implemented to benefit from proper inheritance
- Moves all error definitions into a single file
- Changes the error constructor to take an options object, rather than needing the arguments to be passed in the correct order.
- Provides a wrapper so that any errors that haven't already been converted to GhostErrors get converted before they are displayed.

Summary of changes:

* 🐛  set NODE_ENV in config handler
* ✨  add GhostError implementation (core/server/errors.js)
  - register all errors in one file
  - inheritance from GhostError
  - option pattern
* 🔥  remove all error files
* ✨  wrap all errors into GhostError in case of HTTP
* 🎨  adaptions
  - option pattern for errors
  - use GhostError when needed
* 🎨  revert debug deletion and add TODO for error id's
  • Loading branch information
kirrg001 authored and ErisDS committed Oct 6, 2016
1 parent 32700a0 commit d81bc91
Show file tree
Hide file tree
Showing 108 changed files with 766 additions and 810 deletions.
58 changes: 30 additions & 28 deletions core/server/api/authentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ var _ = require('lodash'),
globalUtils = require('../utils'),
utils = require('./utils'),
errors = require('../errors'),
logging = require('../logging'),
models = require('../models'),
logging = require('../logging'),
events = require('../events'),
config = require('../config'),
i18n = require('../i18n'),
Expand Down Expand Up @@ -43,7 +43,7 @@ function assertSetupCompleted(status) {
notCompleted = i18n.t('errors.api.authentication.setupMustBeCompleted');

function throwReason(reason) {
throw new errors.NoPermissionError(reason);
throw new errors.NoPermissionError({message: reason});
}

if (isSetup) {
Expand Down Expand Up @@ -78,9 +78,9 @@ function setupTasks(setupData) {

return User.findOne({role: 'Owner', status: 'all'}).then(function then(owner) {
if (!owner) {
throw new errors.InternalServerError(
i18n.t('errors.api.authentication.setupUnableToRun')
);
throw new errors.GhostError({
message: i18n.t('errors.api.authentication.setupUnableToRun')
});
}

return User.setup(userData, _.extend({id: owner.id}, context));
Expand Down Expand Up @@ -175,9 +175,9 @@ authentication = {
var email = data.passwordreset[0].email;

if (typeof email !== 'string' || !validator.isEmail(email)) {
throw new errors.BadRequestError(
i18n.t('errors.api.authentication.noEmailProvided')
);
throw new errors.BadRequestError({
message: i18n.t('errors.api.authentication.noEmailProvided')
});
}

return email;
Expand Down Expand Up @@ -274,8 +274,8 @@ authentication = {
ne2Password: ne2Password,
dbHash: response.settings[0].value
});
}).catch(function (error) {
throw new errors.UnauthorizedError(error.message);
}).catch(function (err) {
throw new errors.UnauthorizedError({err: err});
});
}

Expand Down Expand Up @@ -309,19 +309,19 @@ authentication = {
return utils.checkObject(invitation, 'invitation')
.then(function () {
if (!invitation.invitation[0].token) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.api.authentication.noTokenProvided')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.api.authentication.noTokenProvided')}));
}

if (!invitation.invitation[0].email) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.api.authentication.noEmailProvided')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.api.authentication.noEmailProvided')}));
}

if (!invitation.invitation[0].password) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.api.authentication.noPasswordProvided')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.api.authentication.noPasswordProvided')}));
}

if (!invitation.invitation[0].name) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.api.authentication.noNameProvided')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.api.authentication.noNameProvided')}));
}

return invitation;
Expand All @@ -336,11 +336,11 @@ authentication = {
invite = _invite;

if (!invite) {
throw new errors.NotFoundError(i18n.t('errors.api.invites.inviteNotFound'));
throw new errors.NotFoundError({message: i18n.t('errors.api.invites.inviteNotFound')});
}

if (invite.get('expires') < Date.now()) {
throw new errors.NotFoundError(i18n.t('errors.api.invites.inviteExpired'));
throw new errors.NotFoundError({message: i18n.t('errors.api.invites.inviteExpired')});
}

return models.User.add({
Expand Down Expand Up @@ -386,9 +386,9 @@ authentication = {
var email = options.email;

if (typeof email !== 'string' || !validator.isEmail(email)) {
throw new errors.BadRequestError(
i18n.t('errors.api.authentication.invalidEmailReceived')
);
throw new errors.BadRequestError({
message: i18n.t('errors.api.authentication.invalidEmailReceived')
});
}

return email;
Expand Down Expand Up @@ -489,10 +489,12 @@ authentication = {
}]
};

apiMail.send(payload, {context: {internal: true}}).catch(function (err) {
err.context = i18n.t('errors.api.authentication.unableToSendWelcomeEmail');
err.help = i18n.t('errors.api.authentication.checkEmailConfigInstructions', {url: 'http://support.ghost.org/mail/'});
logging.error(err);
apiMail.send(payload, {context: {internal: true}}).catch(function (error) {
logging.error(new errors.EmailError({
err: error,
context: i18n.t('errors.api.authentication.unableToSendWelcomeEmail'),
help: i18n.t('errors.api.authentication.checkEmailConfigInstructions', {url: 'http://support.ghost.org/mail/'})
}));
});
})
.return(setupUser);
Expand Down Expand Up @@ -524,7 +526,7 @@ authentication = {

function processArgs(setupDetails, options) {
if (!options.context || !options.context.user) {
throw new errors.NoPermissionError(i18n.t('errors.api.authentication.notTheBlogOwner'));
throw new errors.NoPermissionError({message: i18n.t('errors.api.authentication.notTheBlogOwner')});
}

return _.assign({setupDetails: setupDetails}, options);
Expand All @@ -534,7 +536,7 @@ authentication = {
return models.User.findOne({role: 'Owner', status: 'all'})
.then(function (owner) {
if (owner.id !== options.context.user) {
throw new errors.NoPermissionError(i18n.t('errors.api.authentication.notTheBlogOwner'));
throw new errors.NoPermissionError({message: i18n.t('errors.api.authentication.notTheBlogOwner')});
}

return options.setupDetails;
Expand Down Expand Up @@ -591,9 +593,9 @@ authentication = {
return destroyToken(providers.pop(), options, providers);
})
.catch(function () {
throw new errors.TokenRevocationError(
i18n.t('errors.api.authentication.tokenRevocationFailed')
);
throw new errors.TokenRevocationError({
message: i18n.t('errors.api.authentication.tokenRevocationFailed')
});
});
}

Expand Down
2 changes: 1 addition & 1 deletion core/server/api/clients.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ clients = {
return {clients: [result.toJSON(options)]};
}

return Promise.reject(new errors.NotFoundError(i18n.t('common.api.clients.clientNotFound')));
return Promise.reject(new errors.NotFoundError({message: i18n.t('common.api.clients.clientNotFound')}));
});
}
};
Expand Down
8 changes: 4 additions & 4 deletions core/server/api/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ db = {
function exportContent() {
return exporter.doExport().then(function (exportedData) {
return {db: [exportedData]};
}).catch(function (error) {
return Promise.reject(new errors.InternalServerError(error.message || error));
}).catch(function (err) {
return Promise.reject(new errors.GhostError({err: err}));
});
}

Expand Down Expand Up @@ -99,8 +99,8 @@ db = {
return Promise.each(collections, function then(Collection) {
return Collection.invokeThen('destroy');
}).return({db: []})
.catch(function (error) {
throw new errors.InternalServerError(error.message || error);
.catch(function (err) {
throw new errors.GhostError({err: err});
});
}

Expand Down
12 changes: 6 additions & 6 deletions core/server/api/invites.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ invites = {
return {invites: [result.toJSON(options)]};
}

return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.invites.inviteNotFound')));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.api.invites.inviteNotFound')}));
});
},

Expand All @@ -65,7 +65,7 @@ invites = {
return dataProvider.Invite.findOne({id: options.id}, _.omit(options, ['data']))
.then(function (invite) {
if (!invite) {
throw new errors.NotFoundError(i18n.t('errors.api.invites.inviteNotFound'));
throw new errors.NotFoundError({message: i18n.t('errors.api.invites.inviteNotFound')});
}

return invite.destroy(options).return(null);
Expand Down Expand Up @@ -94,7 +94,7 @@ invites = {
return dataProvider.User.findOne({id: loggedInUser}, options)
.then(function (user) {
if (!user) {
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.users.userNotFound')));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.api.users.userNotFound')}));
}

loggedInUser = user;
Expand Down Expand Up @@ -172,11 +172,11 @@ invites = {
var roleId;

if (!options.data.invites[0].email) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.api.invites.emailIsRequired')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.api.invites.emailIsRequired')}));
}

if (!options.data.invites[0].roles || !options.data.invites[0].roles[0]) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.api.invites.roleIsRequired')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.api.invites.roleIsRequired')}));
}

roleId = parseInt(options.data.invites[0].roles[0].id || options.data.invites[0].roles[0], 10);
Expand All @@ -185,7 +185,7 @@ invites = {
// Make sure user is allowed to add a user with this role
return dataProvider.Role.findOne({id: roleId}).then(function (role) {
if (role.get('name') === 'Owner') {
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.invites.notAllowedToInviteOwner')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.api.invites.notAllowedToInviteOwner')}));
}
}).then(function () {
return options;
Expand Down
2 changes: 1 addition & 1 deletion core/server/api/mail.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ function sendMail(object) {
);
}

return Promise.reject(new errors.EmailError(err.message));
return Promise.reject(new errors.EmailError({err: err}));
});
}

Expand Down
17 changes: 10 additions & 7 deletions core/server/api/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ notifications = {
return canThis(options.context).browse.notification().then(function () {
return {notifications: notificationsStore};
}, function () {
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.notifications.noPermissionToBrowseNotif')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.api.notifications.noPermissionToBrowseNotif')}));
});
},

Expand Down Expand Up @@ -72,7 +72,7 @@ notifications = {
return canThis(options.context).add.notification().then(function () {
return options;
}, function () {
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.notifications.noPermissionToAddNotif')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.api.notifications.noPermissionToAddNotif')}));
});
}

Expand Down Expand Up @@ -155,7 +155,7 @@ notifications = {
return canThis(options.context).destroy.notification().then(function () {
return options;
}, function () {
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.notifications.noPermissionToDestroyNotif')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.api.notifications.noPermissionToDestroyNotif')}));
});
}

Expand All @@ -166,12 +166,12 @@ notifications = {

if (notification && !notification.dismissible) {
return Promise.reject(
new errors.NoPermissionError(i18n.t('errors.api.notifications.noPermissionToDismissNotif'))
new errors.NoPermissionError({message: i18n.t('errors.api.notifications.noPermissionToDismissNotif')})
);
}

if (!notification) {
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.notifications.notificationDoesNotExist')));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.api.notifications.notificationDoesNotExist')}));
}

notificationsStore = _.reject(notificationsStore, function (element) {
Expand Down Expand Up @@ -206,8 +206,11 @@ notifications = {
notificationCounter = 0;

return notificationsStore;
}, function () {
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.notifications.noPermissionToDestroyNotif')));
}, function (err) {
return Promise.reject(new errors.NoPermissionError({
err: err,
context: i18n.t('errors.api.notifications.noPermissionToDestroyNotif')
}));
});
}
};
Expand Down
6 changes: 3 additions & 3 deletions core/server/api/posts.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ posts = {
return {posts: [result.toJSON(options)]};
}

return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.posts.postNotFound')));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.api.posts.postNotFound')}));
});
},

Expand Down Expand Up @@ -154,7 +154,7 @@ posts = {
return {posts: [post]};
}

return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.posts.postNotFound')));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.api.posts.postNotFound')}));
});
},

Expand Down Expand Up @@ -223,7 +223,7 @@ posts = {
return Post.findOne(data, fetchOpts).then(function () {
return Post.destroy(options).return(null);
}).catch(Post.NotFoundError, function () {
throw new errors.NotFoundError(i18n.t('errors.api.posts.postNotFound'));
throw new errors.NotFoundError({message: i18n.t('errors.api.posts.postNotFound')});
});
}

Expand Down
6 changes: 3 additions & 3 deletions core/server/api/schedules.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ exports.publishPost = function publishPost(object, options) {

// CASE: only the scheduler client is allowed to publish (hardcoded because of missing client permission system)
if (!options.context || !options.context.client || options.context.client !== 'ghost-scheduler') {
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.permissions.noPermissionToAction')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.permissions.noPermissionToAction')}));
}

options.context = {internal: true};
Expand All @@ -41,11 +41,11 @@ exports.publishPost = function publishPost(object, options) {
publishedAtMoment = moment(post.published_at);

if (publishedAtMoment.diff(moment(), 'minutes') > publishAPostBySchedulerToleranceInMinutes) {
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.job.notFound')));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.api.job.notFound')}));
}

if (publishedAtMoment.diff(moment(), 'minutes') < publishAPostBySchedulerToleranceInMinutes * -1 && object.force !== true) {
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.job.publishInThePast')));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.api.job.publishInThePast')}));
}

return apiPosts.edit({posts: [{status: 'published'}]}, _.pick(cleanOptions, ['context', 'id']));
Expand Down
Loading

0 comments on commit d81bc91

Please sign in to comment.