0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-18 02:21:47 -05:00

Error creation (#7477)

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
This commit is contained in:
Katharina Irrgang 2016-10-06 14:27:35 +02:00 committed by Hannah Wolfe
parent 32700a0e5a
commit d81bc91bd2
108 changed files with 766 additions and 810 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -38,7 +38,7 @@ function sendMail(object) {
);
}
return Promise.reject(new errors.EmailError(err.message));
return Promise.reject(new errors.EmailError({err: err}));
});
}

View file

@ -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')}));
});
},
@ -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')}));
});
}
@ -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')}));
});
}
@ -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) {
@ -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')
}));
});
}
};

View file

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

View file

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

View file

@ -43,10 +43,12 @@ updateConfigCache = function () {
try {
labsValue = JSON.parse(settingsCache.labs.value);
} catch (err) {
err.message = i18n.t('errors.api.settings.invalidJsonInLabs');
err.context = i18n.t('errors.api.settings.labsColumnCouldNotBeParsed');
err.help = i18n.t('errors.api.settings.tryUpdatingLabs');
logging.error(err);
logging.error(new errors.GhostError({
err: err,
message: i18n.t('errors.api.settings.invalidJsonInLabs'),
context: i18n.t('errors.api.settings.labsColumnCouldNotBeParsed'),
help: i18n.t('errors.api.settings.tryUpdatingLabs')
}));
}
}
@ -250,7 +252,7 @@ populateDefaultSetting = function (key) {
}
// TODO: Different kind of error?
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.settings.problemFindingSetting', {key: key})));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.api.settings.problemFindingSetting', {key: key})}));
});
};
@ -265,12 +267,12 @@ canEditAllSettings = function (settingsInfo, options) {
var checkSettingPermissions = function (setting) {
if (setting.type === 'core' && !(options.context && options.context.internal)) {
return Promise.reject(
new errors.NoPermissionError(i18n.t('errors.api.settings.accessCoreSettingFromExtReq'))
new errors.NoPermissionError({message: i18n.t('errors.api.settings.accessCoreSettingFromExtReq')})
);
}
return canThis(options.context).edit.setting(setting.key).catch(function () {
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.settings.noPermissionToEditSettings')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.api.settings.noPermissionToEditSettings')}));
});
},
checks = _.map(settingsInfo, function (settingInfo) {
@ -349,7 +351,7 @@ settings = {
if (setting.type === 'core' && !(options.context && options.context.internal)) {
return Promise.reject(
new errors.NoPermissionError(i18n.t('errors.api.settings.accessCoreSettingFromExtReq'))
new errors.NoPermissionError({message: i18n.t('errors.api.settings.accessCoreSettingFromExtReq')})
);
}
@ -360,7 +362,7 @@ settings = {
return canThis(options.context).read.setting(options.key).then(function () {
return settingsResult(result);
}, function () {
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.settings.noPermissionToReadSettings')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.api.settings.noPermissionToReadSettings')}));
});
};

View file

@ -46,7 +46,7 @@ slugs = {
*/
function checkAllowedTypes(options) {
if (allowedTypes[options.type] === undefined) {
return Promise.reject(new errors.BadRequestError(i18n.t('errors.api.slugs.unknownSlugType', {type: options.type})));
return Promise.reject(new errors.BadRequestError({message: i18n.t('errors.api.slugs.unknownSlugType', {type: options.type})}));
}
return options;
}
@ -72,7 +72,7 @@ slugs = {
// Pipeline calls each task passing the result of one to be the arguments for the next
return pipeline(tasks, options).then(function (slug) {
if (!slug) {
return Promise.reject(new errors.InternalServerError(i18n.t('errors.api.slugs.couldNotGenerateSlug')));
return Promise.reject(new errors.GhostError({message: i18n.t('errors.api.slugs.couldNotGenerateSlug')}));
}
return {slugs: [{slug: slug}]};

View file

@ -80,7 +80,7 @@ subscribers = {
return {subscribers: [result.toJSON(options)]};
}
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.subscribers.subscriberNotFound')));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.api.subscribers.subscriberNotFound')}));
});
},
@ -105,12 +105,12 @@ subscribers = {
// we don't expose this information
return Promise.resolve(subscriber);
} else if (subscriber) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.api.subscribers.subscriberAlreadyExists')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.api.subscribers.subscriberAlreadyExists')}));
}
return dataProvider.Subscriber.add(options.data.subscribers[0], _.omit(options, ['data'])).catch(function (error) {
if (error.code && error.message.toLowerCase().indexOf('unique') !== -1) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.api.subscribers.subscriberAlreadyExists')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.api.subscribers.subscriberAlreadyExists')}));
}
return Promise.reject(error);
@ -167,7 +167,7 @@ subscribers = {
return {subscribers: [subscriber]};
}
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.subscribers.subscriberNotFound')));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.api.subscribers.subscriberNotFound')}));
});
},
@ -241,8 +241,8 @@ subscribers = {
function exportSubscribers() {
return dataProvider.Subscriber.findPage(options).then(function (data) {
return formatCSV(data.subscribers);
}).catch(function (error) {
return Promise.reject(new errors.InternalServerError(error.message || error));
}).catch(function (err) {
return Promise.reject(new errors.GhostError({err: err}));
});
}

View file

@ -81,7 +81,7 @@ tags = {
return {tags: [result.toJSON(options)]};
}
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.tags.tagNotFound')));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.api.tags.tagNotFound')}));
});
},
@ -155,7 +155,7 @@ tags = {
return {tags: [tag]};
}
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.tags.tagNotFound')));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.api.tags.tagNotFound')}));
});
},

View file

@ -35,15 +35,15 @@ themes = {
options.originalname = options.originalname.toLowerCase();
var storageAdapter = storage.getStorage('themes'),
zip = {
path: options.path,
name: options.originalname,
shortName: storageAdapter.getSanitizedFileName(options.originalname.split('.zip')[0])
}, theme;
zip = {
path: options.path,
name: options.originalname,
shortName: storageAdapter.getSanitizedFileName(options.originalname.split('.zip')[0])
}, theme;
// check if zip name is casper.zip
if (zip.name === 'casper.zip') {
throw new errors.ValidationError(i18n.t('errors.api.themes.overrideCasper'));
throw new errors.ValidationError({message: i18n.t('errors.api.themes.overrideCasper')});
}
return apiUtils.handlePermissions('themes', 'add')(options)
@ -58,10 +58,10 @@ themes = {
return;
}
throw new errors.ThemeValidationError(
i18n.t('errors.api.themes.invalidTheme'),
theme.results.error
);
throw new errors.ThemeValidationError({
message: i18n.t('errors.api.themes.invalidTheme'),
errorDetails: theme.results.error
});
})
.then(function () {
return storageAdapter.exists(config.getContentPath('themes') + '/' + zip.shortName);
@ -104,7 +104,7 @@ themes = {
// happens in background
Promise.promisify(fs.removeSync)(zip.path)
.catch(function (err) {
logging.error(err);
logging.error(new errors.GhostError({err: err}));
});
// remove extracted dir from gscan
@ -112,7 +112,7 @@ themes = {
if (theme) {
Promise.promisify(fs.removeSync)(theme.path)
.catch(function (err) {
logging.error(err);
logging.error(new errors.GhostError({err: err}));
});
}
});
@ -124,7 +124,7 @@ themes = {
storageAdapter = storage.getStorage('themes');
if (!theme) {
return Promise.reject(new errors.BadRequestError(i18n.t('errors.api.themes.invalidRequest')));
return Promise.reject(new errors.BadRequestError({message: i18n.t('errors.api.themes.invalidRequest')}));
}
return apiUtils.handlePermissions('themes', 'read')(options)
@ -146,13 +146,13 @@ themes = {
return apiUtils.handlePermissions('themes', 'destroy')(options)
.then(function () {
if (name === 'casper') {
throw new errors.ValidationError(i18n.t('errors.api.themes.destroyCasper'));
throw new errors.ValidationError({message: i18n.t('errors.api.themes.destroyCasper')});
}
theme = config.get('paths').availableThemes[name];
if (!theme) {
throw new errors.NotFoundError(i18n.t('errors.api.themes.themeDoesNotExist'));
throw new errors.NotFoundError({message: i18n.t('errors.api.themes.themeDoesNotExist')});
}
events.emit('theme.deleted', name);

View file

@ -90,7 +90,7 @@ users = {
return {users: [result.toJSON(options)]};
}
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')}));
});
},
@ -145,14 +145,14 @@ users = {
var contextRoleId = contextUser.related('roles').toJSON(options)[0].id;
if (roleId !== contextRoleId && editedUserId === contextUser.id) {
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.users.cannotChangeOwnRole')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.api.users.cannotChangeOwnRole')}));
}
return dataProvider.User.findOne({role: 'Owner'}).then(function (owner) {
if (contextUser.id !== owner.id) {
if (editedUserId === owner.id) {
if (owner.related('roles').at(0).id !== roleId) {
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.api.users.cannotChangeOwnersRole')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.api.users.cannotChangeOwnersRole')}));
}
} else if (roleId !== contextRoleId) {
return canThis(options.context).assign.role(role).then(function () {
@ -165,7 +165,10 @@ users = {
});
});
}).catch(function handleError(err) {
return Promise.reject(new errors.NoPermissionError(err.message, i18n.t('errors.api.users.noPermissionToEditUser')));
return Promise.reject(new errors.NoPermissionError({
err: err,
context: i18n.t('errors.api.users.noPermissionToEditUser')
}));
});
}
@ -192,7 +195,7 @@ users = {
return {users: [result.toJSON(options)]};
}
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')}));
});
},
@ -215,7 +218,10 @@ users = {
options.status = 'all';
return options;
}).catch(function handleError(err) {
return Promise.reject(new errors.NoPermissionError(err.message, i18n.t('errors.api.users.noPermissionToDestroyUser')));
return Promise.reject(new errors.NoPermissionError({
err: err,
context: i18n.t('errors.api.users.noPermissionToDestroyUser')
}));
});
}
@ -236,7 +242,9 @@ users = {
return dataProvider.User.destroy(options);
}).return(null);
}).catch(function (err) {
return Promise.reject(new errors.NoPermissionError(err.message));
return Promise.reject(new errors.NoPermissionError({
err: err
}));
});
}
@ -271,7 +279,10 @@ users = {
return canThis(options.context).edit.user(options.data.password[0].user_id).then(function permissionGranted() {
return options;
}).catch(function (err) {
return Promise.reject(new errors.NoPermissionError(err.message, i18n.t('errors.api.users.noPermissionToChangeUsersPwd')));
return Promise.reject(new errors.NoPermissionError({
err: err,
context: i18n.t('errors.api.users.noPermissionToChangeUsersPwd')
}));
});
}

View file

@ -211,11 +211,15 @@ utils = {
return permsPromise.then(function permissionGranted() {
return options;
}).catch(errors.NoPermissionError, function handleNoPermissionError(error) {
// pimp error message
error.message = i18n.t('errors.api.utils.noPermissionToCall', {method: method, docName: docName});
// forward error to next catch()
return Promise.reject(error);
}).catch(function handleNoPermissionError(err) {
if (err instanceof errors.NoPermissionError) {
err.message = i18n.t('errors.api.utils.noPermissionToCall', {method: method, docName: docName});
return Promise.reject(err);
}
return Promise.reject(new errors.GhostError({
err: err
}));
});
};
},
@ -271,7 +275,9 @@ utils = {
*/
checkObject: function (object, docName, editId) {
if (_.isEmpty(object) || _.isEmpty(object[docName]) || _.isEmpty(object[docName][0])) {
return Promise.reject(new errors.BadRequestError(i18n.t('errors.api.utils.noRootKeyProvided', {docName: docName})));
return Promise.reject(new errors.BadRequestError({
message: i18n.t('errors.api.utils.noRootKeyProvided', {docName: docName})
}));
}
// convert author property to author_id to match the name in the database
@ -292,7 +298,9 @@ utils = {
});
if (editId && object[docName][0].id && parseInt(editId, 10) !== parseInt(object[docName][0].id, 10)) {
return Promise.reject(new errors.BadRequestError(i18n.t('errors.api.utils.invalidIdProvided')));
return Promise.reject(new errors.BadRequestError({
message: i18n.t('errors.api.utils.invalidIdProvided')
}));
}
return Promise.resolve(object);

View file

@ -13,6 +13,8 @@ var hbs = require('express-hbs'),
sanitizeHtml = require('sanitize-html'),
config = require('../../../../config'),
logging = require('../../../../logging'),
i18n = require('../../../../i18n'),
errors = require('../../../../errors'),
makeAbsoluteUrl = require('../../../../utils/make-absolute-urls'),
cheerio = require('cheerio'),
amperize = new Amperize(),
@ -126,10 +128,13 @@ function getAmperizeHTML(html, post) {
amperize.parse(html, function (err, res) {
if (err) {
if (err.src) {
err.context = 'AMP HTML couldn\'t get parsed: ' + err.src;
logging.error(err);
logging.error(new errors.GhostError({
err: err,
context: 'AMP HTML couldn\'t get parsed: ' + err.src,
help: i18n.t('errors.apps.appWillNotBeLoaded.help')
}));
} else {
logging.error(err);
logging.error(new errors.GhostError({err: err}));
}
// save it in cache to prevent multiple calls to Amperize until

View file

@ -182,9 +182,10 @@ describe('AMP getPostData', function () {
done();
});
});
it('should return error if postlookup returns NotFoundError', function (done) {
postLookupStub = sandbox.stub();
postLookupStub.returns(new Promise.reject(new errors.NotFoundError('not found')));
postLookupStub.returns(new Promise.reject(new errors.NotFoundError({message: 'not found'})));
ampController.__set__('postLookup', postLookupStub);

View file

@ -2,6 +2,7 @@
var _ = require('lodash'),
Promise = require('bluebird'),
logging = require('../logging'),
errors = require('../errors'),
api = require('../api'),
loader = require('./loader'),
i18n = require('../i18n'),
@ -47,10 +48,11 @@ module.exports = {
appsToLoad = appsToLoad.concat(config.get('internalApps'));
});
} catch (err) {
err.message = i18n.t('errors.apps.failedToParseActiveAppsSettings.error', {message: err.message});
err.help = i18n.t('errors.apps.failedToParseActiveAppsSettings.context');
err.context = i18n.t('errors.apps.failedToParseActiveAppsSettings.help');
logging.error(err);
logging.error(new errors.GhostError({
err: err,
context: i18n.t('errors.apps.failedToParseActiveAppsSettings.context'),
help: i18n.t('errors.apps.failedToParseActiveAppsSettings.help')
}));
return Promise.resolve();
}
@ -87,9 +89,11 @@ module.exports = {
// Extend the loadedApps onto the available apps
_.extend(availableApps, loadedApps);
}).catch(function (err) {
err.context = i18n.t('errors.apps.appWillNotBeLoaded.error');
err.help = i18n.t('errors.apps.appWillNotBeLoaded.help');
logging.error(err);
logging.error(new errors.GhostError({
err: err,
context: i18n.t('errors.apps.appWillNotBeLoaded.error'),
help: i18n.t('errors.apps.appWillNotBeLoaded.help')
}));
});
});
},

View file

@ -1,5 +1,6 @@
var config = require('../../config'),
utils = require('../../utils'),
errors = require('../../errors'),
logging = require('../../logging'),
i18n = require('../../i18n'),
middleware = require('./lib/middleware'),
@ -8,19 +9,19 @@ var config = require('../../config'),
module.exports = {
activate: function activate(ghost) {
var err, paths;
var paths;
if (utils.url.getSubdir()) {
paths = utils.url.getSubdir().split('/');
if (paths.pop() === config.get('routeKeywords').private) {
err = new Error();
err.message = i18n.t('errors.config.urlCannotContainPrivateSubdir.error');
err.context = i18n.t('errors.config.urlCannotContainPrivateSubdir.description');
err.help = i18n.t('errors.config.urlCannotContainPrivateSubdir.help');
logging.error(err);
logging.error(new errors.GhostError({
message: i18n.t('errors.config.urlCannotContainPrivateSubdir.error'),
context: i18n.t('errors.config.urlCannotContainPrivateSubdir.description'),
help: i18n.t('errors.config.urlCannotContainPrivateSubdir.help')
}));
// @TODO: why?
// @TODO: why
process.exit(0);
}
}

View file

@ -7,7 +7,7 @@ var _ = require('lodash'),
config = require('../../../config'),
api = require('../../../api'),
errors = require('../../../errors'),
logging = require('../../../logging'),
logging = require('../../../logging'),
utils = require('../../../utils'),
i18n = require('../../../i18n'),
privateRoute = '/' + config.get('routeKeywords').private + '/',
@ -56,7 +56,7 @@ privateBlogging = {
if (req.path.lastIndexOf('/rss/', 0) === 0 ||
req.path.lastIndexOf('/rss/') === req.url.length - 5 ||
(req.path.lastIndexOf('/sitemap', 0) === 0 && req.path.lastIndexOf('.xml') === req.path.length - 4)) {
return next(new errors.NotFoundError(i18n.t('errors.errors.pageNotFound')));
return next(new errors.NotFoundError({message: i18n.t('errors.errors.pageNotFound')}));
} else if (req.url.lastIndexOf('/robots.txt', 0) === 0) {
fs.readFile(path.resolve(__dirname, '../', 'robots.txt'), function readFile(err, buf) {
if (err) {
@ -146,8 +146,7 @@ privateBlogging = {
ipCount = '',
message = i18n.t('errors.middleware.spamprevention.tooManyAttempts'),
deniedRateLimit = '',
password = req.body.password,
err;
password = req.body.password;
if (password) {
protectedSecurity.push({ip: remoteAddress, time: currentTime});
@ -167,10 +166,10 @@ privateBlogging = {
deniedRateLimit = (ipCount[remoteAddress] > rateProtectedAttempts);
if (deniedRateLimit) {
err = new Error();
err.message = i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error', {rfa: rateProtectedAttempts, rfp: rateProtectedPeriod});
err.context = i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.context');
logging.error(err);
logging.error(new errors.GhostError({
message: i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error', {rfa: rateProtectedAttempts, rfp: rateProtectedPeriod}),
context: i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.context')
}));
message += rateProtectedPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater');

View file

@ -71,7 +71,7 @@ function storeSubscriber(req, res, next) {
req.body.status = 'subscribed';
if (_.isEmpty(req.body.email)) {
return next(new errors.ValidationError('Email cannot be blank.'));
return next(new errors.ValidationError({message: 'Email cannot be blank.'}));
}
return api.subscribers.add({subscribers: [req.body]}, {context: {external: true}})

View file

@ -86,11 +86,11 @@ strategies = {
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({
@ -113,7 +113,7 @@ strategies = {
return models.User.findOne({slug: 'ghost-owner', status: 'all'}, options)
.then(function fetchedOwner(owner) {
if (!owner) {
throw new errors.NotFoundError(i18n.t('errors.models.user.userNotFound'));
throw new errors.NotFoundError({message: i18n.t('errors.models.user.userNotFound')});
}
return models.User.edit({

View file

@ -44,11 +44,11 @@ authenticate = {
}
if (!req.body.client_id || !req.body.client_secret) {
return next(new errors.UnauthorizedError(
i18n.t('errors.middleware.auth.accessDenied')),
i18n.t('errors.middleware.auth.clientCredentialsNotProvided'),
i18n.t('errors.middleware.auth.forInformationRead', {url: 'http://api.ghost.org/docs/client-authentication'})
);
return next(new errors.UnauthorizedError({
message: i18n.t('errors.middleware.auth.accessDenied'),
context: i18n.t('errors.middleware.auth.clientCredentialsNotProvided'),
help: i18n.t('errors.middleware.auth.forInformationRead', {url: 'http://api.ghost.org/docs/client-authentication'})
}));
}
return passport.authenticate(['oauth2-client-password'], {session: false, failWithError: false},
@ -62,11 +62,11 @@ authenticate = {
delete req.body.client_secret;
if (!client) {
return next(new errors.UnauthorizedError(
i18n.t('errors.middleware.auth.accessDenied')),
i18n.t('errors.middleware.auth.clientCredentialsNotValid'),
i18n.t('errors.middleware.auth.forInformationRead', {url: 'http://api.ghost.org/docs/client-authentication'})
);
return next(new errors.UnauthorizedError({
message: i18n.t('errors.middleware.auth.accessDenied'),
context: i18n.t('errors.middleware.auth.clientCredentialsNotValid'),
help: i18n.t('errors.middleware.auth.forInformationRead', {url: 'http://api.ghost.org/docs/client-authentication'})
}));
}
req.client = client;
@ -92,13 +92,17 @@ authenticate = {
events.emit('user.authenticated', user);
return next(null, user, info);
} else if (isBearerAutorizationHeader(req)) {
return next(new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDenied')));
return next(new errors.UnauthorizedError({
message: i18n.t('errors.middleware.auth.accessDenied')
}));
} else if (req.client) {
req.user = {id: 0};
return next();
}
return next(new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDenied')));
return next(new errors.UnauthorizedError({
message: i18n.t('errors.middleware.auth.accessDenied')
}));
}
)(req, res, next);
},
@ -108,7 +112,7 @@ authenticate = {
req.query.code = req.body.authorizationCode;
if (!req.query.code) {
return next(new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDenied')));
return next(new errors.UnauthorizedError({message: i18n.t('errors.middleware.auth.accessDenied')}));
}
passport.authenticate('ghost', {session: false, failWithError: false}, function authenticate(err, user, info) {
@ -117,7 +121,7 @@ authenticate = {
}
if (!user) {
return next(new errors.UnauthorizedError(i18n.t('errors.middleware.auth.accessDenied')));
return next(new errors.UnauthorizedError({message: i18n.t('errors.middleware.auth.accessDenied')}));
}
req.authInfo = info;

View file

@ -10,7 +10,7 @@ authorize = {
if (req.user && req.user.id) {
return next();
} else {
return next(new errors.NoPermissionError(i18n.t('errors.middleware.auth.pleaseSignIn')));
return next(new errors.NoPermissionError({message: i18n.t('errors.middleware.auth.pleaseSignIn')}));
}
},
@ -22,7 +22,7 @@ authorize = {
if (req.user && req.user.id) {
return next();
} else {
return next(new errors.NoPermissionError(i18n.t('errors.middleware.auth.pleaseSignIn')));
return next(new errors.NoPermissionError({message: i18n.t('errors.middleware.auth.pleaseSignIn')}));
}
}
}

View file

@ -12,7 +12,7 @@ function exchangeRefreshToken(client, refreshToken, scope, done) {
models.Refreshtoken.findOne({token: refreshToken})
.then(function then(model) {
if (!model) {
return done(new errors.NoPermissionError(i18n.t('errors.middleware.oauth.invalidRefreshToken')), false);
return done(new errors.NoPermissionError({message: i18n.t('errors.middleware.oauth.invalidRefreshToken')}), false);
} else {
var token = model.toJSON(),
accessToken = utils.uid(191),
@ -33,7 +33,7 @@ function exchangeRefreshToken(client, refreshToken, scope, done) {
return done(error, false);
});
} else {
done(new errors.UnauthorizedError(i18n.t('errors.middleware.oauth.refreshTokenExpired')), false);
done(new errors.UnauthorizedError({message: i18n.t('errors.middleware.oauth.refreshTokenExpired')}), false);
}
}
});
@ -44,7 +44,7 @@ function exchangePassword(client, username, password, scope, done) {
models.Client.findOne({slug: client.slug})
.then(function then(client) {
if (!client) {
return done(new errors.NoPermissionError(i18n.t('errors.middleware.oauth.invalidClient')), false);
return done(new errors.NoPermissionError({message: i18n.t('errors.middleware.oauth.invalidClient')}), false);
}
// Validate the user

View file

@ -6,6 +6,7 @@ var ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy
authStrategies = require('./auth-strategies'),
utils = require('../utils'),
errors = require('../errors'),
logging = require('../logging'),
models = require('../models'),
_private = {};
@ -44,11 +45,13 @@ _private.registerClient = function registerClient(options) {
});
})
.catch(function publicClientRegistrationError(err) {
logging.error(err);
if (retryCount < 0) {
return done(new errors.IncorrectUsage(
'Public client registration failed: ' + err.code || err.message,
'Please verify that the url is reachable: ' + ghostOAuth2Strategy.url
));
return done(new errors.IncorrectUsageError({
message: 'Public client registration failed: ' + err.code || err.message,
context: 'Please verify that the url is reachable: ' + ghostOAuth2Strategy.url
}));
}
console.log('RETRY: Public Client Registration...');

View file

@ -4,8 +4,6 @@ var nconf = require('nconf'),
packageInfo = require('../../../package.json'),
env = process.env.NODE_ENV || 'development';
nconf.set('NODE_ENV', env);
/**
* command line arguments
*/
@ -38,6 +36,7 @@ localUtils.makePathsAbsolute.bind(nconf)();
* @TODO: ghost-cli?
*/
nconf.set('ghostVersion', packageInfo.version);
nconf.set('env', env);
module.exports = nconf;
module.exports.isPrivacyDisabled = localUtils.isPrivacyDisabled.bind(nconf);

View file

@ -3,6 +3,7 @@ var debug = require('debug')('ghost:admin:controller'),
Promise = require('bluebird'),
api = require('../api'),
config = require('../config'),
errors = require('../errors'),
logging = require('../logging'),
updateCheck = require('../update-check'),
i18n = require('../i18n'),
@ -67,7 +68,9 @@ adminControllers = {
});
}).finally(function noMatterWhat() {
renderIndex();
}).catch(logging.error);
}).catch(function (err) {
logging.error(new errors.GhostError({err: err}));
});
}
};

View file

@ -7,7 +7,6 @@ var express = require('express'),
utils = require('../../utils'),
channelConfig = require('./channel-config'),
renderChannel = require('./render-channel'),
rssRouter,
channelRouter;
@ -26,7 +25,7 @@ function handlePageParam(req, res, next, page) {
}
} else if (page < 1 || isNaN(page)) {
// Nothing less than 1 is a valid page number, go straight to a 404
return next(new errors.NotFoundError(i18n.t('errors.errors.pageNotFound')));
return next(new errors.NotFoundError({message: i18n.t('errors.errors.pageNotFound')}));
} else {
// Set req.params.page to the already parsed number, and continue
req.params.page = page;

View file

@ -38,7 +38,7 @@ function renderChannel(req, res, next) {
return fetchData(channelOpts).then(function handleResult(result) {
// If page is greater than number of pages we have, go straight to 404
if (pageParam > result.meta.pagination.pages) {
return next(new errors.NotFoundError(i18n.t('errors.errors.pageNotFound')));
return next(new errors.NotFoundError({message: i18n.t('errors.errors.pageNotFound')}));
}
// @TODO: figure out if this can be removed, it's supposed to ensure that absolutely URLs get generated

View file

@ -1,14 +1,13 @@
var _ = require('lodash'),
Promise = require('bluebird'),
db = require('../../data/db'),
commands = require('../schema').commands,
versioning = require('../schema').versioning,
var _ = require('lodash'),
Promise = require('bluebird'),
db = require('../../data/db'),
commands = require('../schema').commands,
versioning = require('../schema').versioning,
serverUtils = require('../../utils'),
errors = require('../../errors'),
logging = require('../../logging'),
settings = require('../../api/settings'),
i18n = require('../../i18n'),
excludedTables = ['accesstokens', 'refreshtokens', 'clients', 'client_trusted_domains'],
modelOptions = {context: {internal: true}},
@ -30,7 +29,7 @@ exportFileName = function exportFileName() {
}
return title + 'ghost.' + datetime + '.json';
}).catch(function (err) {
logging.error(err);
logging.error(new errors.GhostError({err: err}));
return 'ghost.' + datetime + '.json';
});
};
@ -38,7 +37,7 @@ exportFileName = function exportFileName() {
getVersionAndTables = function getVersionAndTables() {
var props = {
version: versioning.getDatabaseVersion(),
tables: commands.getTables()
tables: commands.getTables()
};
return Promise.props(props);
@ -75,7 +74,10 @@ doExport = function doExport() {
return exportData;
}).catch(function (err) {
return Promise.reject(new errors.InternalServerError(err.message, i18n.t('errors.data.export.errorExportingData')));
return Promise.reject(new errors.DataExportError({
err: err,
context: i18n.t('errors.data.export.errorExportingData')
}));
});
};

View file

@ -44,7 +44,7 @@ cleanError = function cleanError(error) {
value = value || 'unknown';
message = message || error.raw.message;
return new errors.DataImportError(message, offendingProperty, value);
return new errors.DataImportError({message: message, property: offendingProperty, value: value});
};
handleErrors = function handleErrors(errorList) {

View file

@ -81,9 +81,11 @@ utils = {
// CASE: external context
userMap[userToMap] = '0';
} else {
throw new errors.DataImportError(
i18n.t('errors.data.import.utils.dataLinkedToUnknownUser', {userToMap: userToMap}), 'user.id', userToMap
);
throw new errors.DataImportError({
message: i18n.t('errors.data.import.utils.dataLinkedToUnknownUser', {userToMap: userToMap}),
property: 'user.id',
value: userToMap
});
}
});

View file

@ -24,7 +24,7 @@ JSONHandler = {
// if importData follows JSON-API format `{ db: [exportedData] }`
if (_.keys(importData).length === 1) {
if (!importData.db || !Array.isArray(importData.db)) {
throw new Error(i18n.t('errors.data.importer.handlers.json.invalidJsonFormat'));
throw new errors.GhostError({message: i18n.t('errors.data.importer.handlers.json.invalidJsonFormat')});
}
importData = importData.db[0];
@ -32,11 +32,11 @@ JSONHandler = {
return importData;
} catch (err) {
return Promise.reject(new errors.BadRequestError(
i18n.t('errors.data.importer.handlers.json.failedToParseImportJson'),
i18n.t('errors.data.importer.handlers.json.apiDbImportContent'),
i18n.t('errors.data.importer.handlers.json.checkImportJsonIsValid')
));
return Promise.reject(new errors.BadRequestError({
err: err,
context: i18n.t('errors.data.importer.handlers.json.apiDbImportContent'),
help: i18n.t('errors.data.importer.handlers.json.checkImportJsonIsValid')
}));
}
});
}

View file

@ -109,9 +109,11 @@ _.extend(ImportManager.prototype, {
_.each(filesToDelete, function (fileToDelete) {
fs.remove(fileToDelete, function (err) {
if (err) {
err.context = i18n.t('errors.data.importer.index.couldNotCleanUpFile.error');
err.help = i18n.t('errors.data.importer.index.couldNotCleanUpFile.context');
logging.error(err);
logging.error(new errors.GhostError({
err: err,
context: i18n.t('errors.data.importer.index.couldNotCleanUpFile.error'),
help: i18n.t('errors.data.importer.index.couldNotCleanUpFile.context')
}));
}
});
});
@ -150,9 +152,7 @@ _.extend(ImportManager.prototype, {
// This is a temporary extra message for the old format roon export which doesn't work with Ghost
if (oldRoonMatches.length > 0) {
throw new errors.UnsupportedMediaTypeError(
i18n.t('errors.data.importer.index.unsupportedRoonExport')
);
throw new errors.UnsupportedMediaTypeError({message: i18n.t('errors.data.importer.index.unsupportedRoonExport')});
}
// If this folder contains importable files or a content or images directory
@ -161,12 +161,10 @@ _.extend(ImportManager.prototype, {
}
if (extMatchesAll.length < 1) {
throw new errors.UnsupportedMediaTypeError(
i18n.t('errors.data.importer.index.noContentToImport'));
throw new errors.UnsupportedMediaTypeError({message: i18n.t('errors.data.importer.index.noContentToImport')});
}
throw new errors.UnsupportedMediaTypeError(
i18n.t('errors.data.importer.index.invalidZipStructure'));
throw new errors.UnsupportedMediaTypeError({message: i18n.t('errors.data.importer.index.invalidZipStructure')});
},
/**
* Use the extract module to extract the given zip file to a temp directory & return the temp directory path
@ -213,8 +211,9 @@ _.extend(ImportManager.prototype, {
this.getExtensionGlob(this.getExtensions(), ALL_DIRS), {cwd: directory}
);
if (extMatchesAll.length < 1 || extMatchesAll[0].split('/') < 1) {
throw new errors.ValidationError(i18n.t('errors.data.importer.index.invalidZipFileBaseDirectory'));
throw new errors.ValidationError({message: i18n.t('errors.data.importer.index.invalidZipFileBaseDirectory')});
}
return extMatchesAll[0].split('/')[0];
},
/**
@ -240,9 +239,9 @@ _.extend(ImportManager.prototype, {
_.each(self.handlers, function (handler) {
if (importData.hasOwnProperty(handler.type)) {
// This limitation is here to reduce the complexity of the importer for now
return Promise.reject(new errors.UnsupportedMediaTypeError(
i18n.t('errors.data.importer.index.zipContainsMultipleDataFormats')
));
return Promise.reject(new errors.UnsupportedMediaTypeError({
message: i18n.t('errors.data.importer.index.zipContainsMultipleDataFormats')
}));
}
var files = self.getFilesFromZip(handler, zipDirectory);
@ -257,9 +256,9 @@ _.extend(ImportManager.prototype, {
});
if (ops.length === 0) {
return Promise.reject(new errors.UnsupportedMediaTypeError(
i18n.t('errors.data.importer.index.noContentToImport')
));
return Promise.reject(new errors.UnsupportedMediaTypeError({
message: i18n.t('errors.data.importer.index.noContentToImport')
}));
}
return sequence(ops).then(function () {

View file

@ -49,7 +49,7 @@ populate = function populate(options) {
});
}).catch(function populateDatabaseError(err) {
logger.warn('rolling back...');
return Promise.reject(new errors.InternalServerError('Unable to populate database: ' + err.message));
return Promise.reject(new errors.GhostError({err: err, context: 'Unable to populate database!'}));
});
};

View file

@ -124,11 +124,13 @@ isDatabaseOutOfDate = function isDatabaseOutOfDate(options) {
// CASE: current database version is lower then we support
if (fromVersion < versioning.canMigrateFromVersion) {
return {error: new errors.DatabaseVersion(
i18n.t('errors.data.versioning.index.cannotMigrate.error'),
i18n.t('errors.data.versioning.index.cannotMigrate.context'),
i18n.t('common.seeLinkForInstructions', {link: 'http://support.ghost.org/how-to-upgrade/'})
)};
return {
error: new errors.DatabaseVersionError({
message: i18n.t('errors.data.versioning.index.cannotMigrate.error'),
context: i18n.t('errors.data.versioning.index.cannotMigrate.context'),
help: i18n.t('common.seeLinkForInstructions', {link: 'http://support.ghost.org/how-to-upgrade/'})
})
};
}
// CASE: the database exists but is out of date
else if (fromVersion < toVersion || forceMigration) {
@ -140,7 +142,7 @@ isDatabaseOutOfDate = function isDatabaseOutOfDate(options) {
}
// CASE: we don't understand the version
else {
return {error: new errors.DatabaseVersion(i18n.t('errors.data.versioning.index.dbVersionNotRecognized'))};
return {error: new errors.DatabaseVersionError({message: i18n.t('errors.data.versioning.index.dbVersionNotRecognized')})};
}
};

View file

@ -9,16 +9,16 @@ module.exports = function bootUp() {
.then(function successHandler(result) {
if (!/^alpha/.test(result)) {
// This database was not created with Ghost alpha, and is not compatible
throw new errors.DatabaseVersion(
'Your database version is not compatible with Ghost 1.0.0 Alpha (master branch)',
'Want to keep your DB? Use Ghost < 1.0.0 or the "stable" branch. Otherwise please delete your DB and restart Ghost',
'More information on the Ghost 1.0.0 Alpha at https://support.ghost.org/v1-0-alpha'
);
throw new errors.DatabaseVersionError({
message: 'Your database version is not compatible with Ghost 1.0.0 Alpha (master branch)',
context: 'Want to keep your DB? Use Ghost < 1.0.0 or the "stable" branch. Otherwise please delete your DB and restart Ghost',
help: 'More information on the Ghost 1.0.0 Alpha at https://support.ghost.org/v1-0-alpha'
});
}
},
// We don't use .catch here, as it would catch the error from the successHandler
function errorHandler(err) {
if (err instanceof errors.DatabaseNotPopulated) {
if (err instanceof errors.DatabaseNotPopulatedError) {
return populate();
}

View file

@ -35,7 +35,7 @@ function getDatabaseVersion() {
});
}
return Promise.reject(new errors.DatabaseNotPopulated(i18n.t('errors.data.versioning.index.databaseNotPopulated')));
return Promise.reject(new errors.DatabaseNotPopulatedError({message: i18n.t('errors.data.versioning.index.databaseNotPopulated')}));
});
}

View file

@ -1,6 +1,8 @@
var https = require('https'),
url = require('url'),
Promise = require('bluebird'),
errors = require('../../errors'),
logging = require('../../logging'),
utils = require('../../utils'),
events = require('../../events'),
logging = require('../../logging'),
@ -32,9 +34,11 @@ function makeRequest(reqOptions, reqPayload) {
req.write(reqPayload);
req.on('error', function (err) {
err.context = i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.error');
err.help = i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.help', {url: 'http://support.ghost.org'});
logging.error(err);
logging.error(new errors.GhostError({
err: err,
context: i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.error'),
help: i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.help', {url: 'http://support.ghost.org'})
}));
});
req.end();

View file

@ -65,7 +65,7 @@ validateSchema = function validateSchema(tableName, model) {
&& schema[tableName][columnKey].nullable !== true) {
if (validator.empty(strVal)) {
message = i18n.t('notices.data.validation.index.valueCannotBeBlank', {tableName: tableName, columnKey: columnKey});
validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey));
validationErrors.push(new errors.ValidationError({message: message, context: tableName + '.' + columnKey}));
}
}
@ -74,7 +74,7 @@ validateSchema = function validateSchema(tableName, model) {
&& schema[tableName][columnKey].type === 'bool') {
if (!(validator.isBoolean(strVal) || validator.empty(strVal))) {
message = i18n.t('notices.data.validation.index.valueMustBeBoolean', {tableName: tableName, columnKey: columnKey});
validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey));
validationErrors.push(new errors.ValidationError({message: message, context: tableName + '.' + columnKey}));
}
}
@ -85,7 +85,7 @@ validateSchema = function validateSchema(tableName, model) {
if (!validator.isLength(strVal, 0, schema[tableName][columnKey].maxlength)) {
message = i18n.t('notices.data.validation.index.valueExceedsMaxLength',
{tableName: tableName, columnKey: columnKey, maxlength: schema[tableName][columnKey].maxlength});
validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey));
validationErrors.push(new errors.ValidationError({message: message, context: tableName + '.' + columnKey}));
}
}
@ -98,7 +98,7 @@ validateSchema = function validateSchema(tableName, model) {
if (schema[tableName][columnKey].hasOwnProperty('type')) {
if (schema[tableName][columnKey].type === 'integer' && !validator.isInt(strVal)) {
message = i18n.t('notices.data.validation.index.valueIsNotInteger', {tableName: tableName, columnKey: columnKey});
validationErrors.push(new errors.ValidationError(message, tableName + '.' + columnKey));
validationErrors.push(new errors.ValidationError({message: message, context: tableName + '.' + columnKey}));
}
}
}
@ -146,7 +146,7 @@ validateActiveTheme = function validateActiveTheme(themeName) {
return availableThemes.then(function then(themes) {
if (!themes.hasOwnProperty(themeName)) {
return Promise.reject(new errors.ValidationError(i18n.t('notices.data.validation.index.themeCannotBeActivated', {themeName: themeName}), 'activeTheme'));
return Promise.reject(new errors.ValidationError({message: i18n.t('notices.data.validation.index.themeCannotBeActivated', {themeName: themeName}), context: 'activeTheme'}));
}
});
};
@ -186,8 +186,9 @@ validate = function validate(value, key, validations) {
// equivalent of validator.isSomething(option1, option2)
if (validator[validationName].apply(validator, validationOptions) !== goodResult) {
validationErrors.push(new errors.ValidationError(i18n.t('notices.data.validation.index.validationFailed',
{validationName: validationName, key: key})));
validationErrors.push(new errors.ValidationError({
message: i18n.t('notices.data.validation.index.validationFailed', {validationName: validationName, key: key})
}));
}
validationOptions.shift();

View file

@ -174,7 +174,7 @@ generate = function generate(req, res, next) {
// If page is greater than number of pages we have, redirect to last page
if (pageParam > maxPage) {
return next(new errors.NotFoundError(i18n.t('errors.errors.pageNotFound')));
return next(new errors.NotFoundError({message: i18n.t('errors.errors.pageNotFound')}));
}
data.version = res.locals.safeVersion;

View file

@ -3,6 +3,7 @@ var _ = require('lodash'),
xml = require('xml'),
config = require('../../config'),
utils = require('../../utils'),
errors = require('../../errors'),
logging = require('../../logging'),
events = require('../../events'),
i18n = require('../../i18n'),
@ -67,11 +68,15 @@ function ping(post) {
req = http.request(options);
req.write(pingXML);
req.on('error', function handleError(err) {
err.context = i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.error');
err.help = i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.help', {url: 'http://support.ghost.org'});
logging.error(err);
logging.error(new errors.GhostError({
err: err,
context: i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.error'),
help: i18n.t('errors.data.xml.xmlrpc.pingUpdateFailed.help', {url: 'http://support.ghost.org'})
}));
});
req.end();
});
}

162
core/server/errors.js Normal file
View file

@ -0,0 +1,162 @@
var _ = require('lodash'),
util = require('util');
function GhostError(options) {
options = options || {};
if (_.isString(options)) {
throw new Error('Please instantiate Errors with the option pattern. e.g. new errors.GhostError({message: ...})');
}
Error.call(this);
Error.captureStackTrace(this, GhostError);
/**
* defaults
* @TODO: I'd like to add the usage of an individual ID to errors, as we have in ignition
*/
this.statusCode = 500;
this.errorType = 'InternalServerError';
this.level = 'normal';
/**
* custom overrides
*/
this.statusCode = options.statusCode || this.statusCode;
this.level = options.level || this.level;
this.context = options.context || this.context;
this.help = options.help || this.help;
this.errorType = this.name = options.errorType || this.errorType;
this.errorDetails = options.errorDetails;
// @TODO: ?
this.property = options.property;
this.value = options.value;
this.message = options.message;
this.hideStack = options.hideStack;
// error to inherit from, override!
if (options.err) {
this.message = options.err.message;
this.stack = options.err.stack;
}
}
// jscs:disable
var errors = {
DataExportError: function DataExportError(options) {
GhostError.call(this, _.merge({
statusCode: 500,
errorType: 'DataExportError'
}, options));
},
DataImportError: function DataImportError(options) {
GhostError.call(this, _.merge({
statusCode: 500,
errorType: 'DataImportError'
}, options));
},
IncorrectUsageError: function IncorrectUsageError(options) {
GhostError.call(this, _.merge({
statusCode: 400,
level: 'critical',
errorType: 'IncorrectUsageError'
}, options));
},
NotFoundError: function NotFoundError(options) {
GhostError.call(this, _.merge({
statusCode: 404,
errorType: 'NotFoundError'
}, options));
},
BadRequestError: function BadRequestError(options) {
GhostError.call(this, _.merge({
statusCode: 400,
errorType: 'BadRequestError'
}, options));
},
DatabaseVersionError: function DatabaseVersionError(options) {
GhostError.call(this, _.merge({
hideStack: true,
statusCode: 500,
errorType: 'DatabaseVersionError'
}, options));
},
DatabaseNotPopulatedError: function DatabaseNotPopulatedError(options) {
GhostError.call(this, _.merge({
statusCode: 500,
errorType: 'DatabaseNotPopulatedError'
}, options));
},
UnauthorizedError: function UnauthorizedError(options) {
GhostError.call(this, _.merge({
statusCode: 401,
errorType: 'UnauthorizedError'
}, options));
},
NoPermissionError: function NoPermissionError(options) {
GhostError.call(this, _.merge({
statusCode: 403,
errorType: 'NoPermissionError'
}, options));
},
ValidationError: function ValidationError(options) {
GhostError.call(this, _.merge({
statusCode: 422,
errorType: 'ValidationError'
}, options));
},
UnsupportedMediaTypeError: function UnsupportedMediaTypeError(options) {
GhostError.call(this, _.merge({
statusCode: 415,
errorType: 'UnsupportedMediaTypeError'
}, options));
},
VersionMismatchError: function VersionMismatchError(options) {
GhostError.call(this, _.merge({
statusCode: 400,
errorType: 'VersionMismatchError'
}, options));
},
TokenRevocationError: function TokenRevocationError(options) {
GhostError.call(this, _.merge({
statusCode: 503,
errorType: 'TokenRevocationError'
}, options));
},
EmailError: function EmailError(options) {
GhostError.call(this, _.merge({
statusCode: 500,
errorType: 'EmailError'
}, options));
},
TooManyRequestsError: function TooManyRequestsError(options) {
GhostError.call(this, _.merge({
statusCode: 429,
errorType: 'TooManyRequestsError'
}, options));
},
MaintenanceError: function MaintenanceError(options) {
GhostError.call(this, _.merge({
statusCode: 503,
errorType: 'MaintenanceError'
}, options));
},
ThemeValidationError: function ThemeValidationError(options) {
GhostError.call(this, _.merge({
statusCode: 422,
errorType: 'ThemeValidationError',
errorDetails: {}
}, options));
}
};
_.each(errors, function (error) {
util.inherits(error, GhostError);
});
module.exports = errors;
module.exports.GhostError = GhostError;

View file

@ -1,16 +0,0 @@
// # Bad request error
// Custom error class with status code and type prefilled.
function BadRequestError(message, context, help) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 400;
this.errorType = this.name;
this.context = context;
this.help = help;
}
BadRequestError.prototype = Object.create(Error.prototype);
BadRequestError.prototype.name = 'BadRequestError';
module.exports = BadRequestError;

View file

@ -1,16 +0,0 @@
// # Data import error
// Custom error class with status code and type prefilled.
function DataImportError(message, offendingProperty, value) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 500;
this.errorType = this.name;
this.property = offendingProperty || undefined;
this.value = value || undefined;
}
DataImportError.prototype = Object.create(Error.prototype);
DataImportError.prototype.name = 'DataImportError';
module.exports = DataImportError;

View file

@ -1,11 +0,0 @@
function DatabaseNotPopulated(message) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 500;
this.errorType = this.name;
}
DatabaseNotPopulated.prototype = Object.create(Error.prototype);
DatabaseNotPopulated.prototype.name = 'DatabaseNotPopulated';
module.exports = DatabaseNotPopulated;

View file

@ -1,13 +0,0 @@
function DatabaseVersion(message, context, help) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 500;
this.errorType = this.name;
this.context = context;
this.help = help;
}
DatabaseVersion.prototype = Object.create(Error.prototype);
DatabaseVersion.prototype.name = 'DatabaseVersion';
module.exports = DatabaseVersion;

View file

@ -1,16 +0,0 @@
// # Email error
// Custom error class with status code and type prefilled.
function EmailError(message, context, help) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 500;
this.errorType = this.name;
this.context = context;
this.help = help;
}
EmailError.prototype = Object.create(Error.prototype);
EmailError.prototype.name = 'EmailError';
module.exports = EmailError;

View file

@ -1,11 +0,0 @@
function IncorrectUsage(message, context) {
this.name = 'IncorrectUsage';
this.stack = new Error().stack;
this.statusCode = 400;
this.errorType = this.name;
this.message = message;
this.context = context;
}
IncorrectUsage.prototype = Object.create(Error.prototype);
module.exports = IncorrectUsage;

View file

@ -1,41 +0,0 @@
// # Errors
/*jslint regexp: true */
var NotFoundError = require('./not-found-error'),
BadRequestError = require('./bad-request-error'),
InternalServerError = require('./internal-server-error'),
NoPermissionError = require('./no-permission-error'),
MethodNotAllowedError = require('./method-not-allowed-error'),
RequestEntityTooLargeError = require('./request-too-large-error'),
UnauthorizedError = require('./unauthorized-error'),
ValidationError = require('./validation-error'),
ThemeValidationError = require('./theme-validation-error'),
UnsupportedMediaTypeError = require('./unsupported-media-type-error'),
EmailError = require('./email-error'),
DataImportError = require('./data-import-error'),
TooManyRequestsError = require('./too-many-requests-error'),
TokenRevocationError = require('./token-revocation-error'),
VersionMismatchError = require('./version-mismatch-error'),
IncorrectUsage = require('./incorrect-usage'),
Maintenance = require('./maintenance'),
DatabaseNotPopulated = require('./database-not-populated'),
DatabaseVersion = require('./database-version');
module.exports.NotFoundError = NotFoundError;
module.exports.BadRequestError = BadRequestError;
module.exports.InternalServerError = InternalServerError;
module.exports.NoPermissionError = NoPermissionError;
module.exports.UnauthorizedError = UnauthorizedError;
module.exports.ValidationError = ValidationError;
module.exports.ThemeValidationError = ThemeValidationError;
module.exports.RequestEntityTooLargeError = RequestEntityTooLargeError;
module.exports.UnsupportedMediaTypeError = UnsupportedMediaTypeError;
module.exports.EmailError = EmailError;
module.exports.DataImportError = DataImportError;
module.exports.MethodNotAllowedError = MethodNotAllowedError;
module.exports.TooManyRequestsError = TooManyRequestsError;
module.exports.TokenRevocationError = TokenRevocationError;
module.exports.VersionMismatchError = VersionMismatchError;
module.exports.IncorrectUsage = IncorrectUsage;
module.exports.Maintenance = Maintenance;
module.exports.DatabaseNotPopulated = DatabaseNotPopulated;
module.exports.DatabaseVersion = DatabaseVersion;

View file

@ -1,16 +0,0 @@
// # Internal Server Error
// Custom error class with status code and type prefilled.
function InternalServerError(message, context, help) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 500;
this.errorType = this.name;
this.context = context;
this.help = help;
}
InternalServerError.prototype = Object.create(Error.prototype);
InternalServerError.prototype.name = 'InternalServerError';
module.exports = InternalServerError;

View file

@ -1,11 +0,0 @@
function Maintenance(message) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 503;
this.errorType = this.name;
}
Maintenance.prototype = Object.create(Error.prototype);
Maintenance.prototype.name = 'Maintenance';
module.exports = Maintenance;

View file

@ -1,14 +0,0 @@
// # Not found error
// Custom error class with status code and type prefilled.
function MethodNotAllowedError(message) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 405;
this.errorType = this.name;
}
MethodNotAllowedError.prototype = Object.create(Error.prototype);
MethodNotAllowedError.prototype.name = 'MethodNotAllowedError';
module.exports = MethodNotAllowedError;

View file

@ -1,14 +0,0 @@
// # No Permission Error
// Custom error class with status code and type prefilled.
function NoPermissionError(message) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 403;
this.errorType = this.name;
}
NoPermissionError.prototype = Object.create(Error.prototype);
NoPermissionError.prototype.name = 'NoPermissionError';
module.exports = NoPermissionError;

View file

@ -1,14 +0,0 @@
// # Not found error
// Custom error class with status code and type prefilled.
function NotFoundError(message) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 404;
this.errorType = this.name;
}
NotFoundError.prototype = Object.create(Error.prototype);
NotFoundError.prototype.name = 'NotFoundError';
module.exports = NotFoundError;

View file

@ -1,14 +0,0 @@
// # Request Entity Too Large Error
// Custom error class with status code and type prefilled.
function RequestEntityTooLargeError(message) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 413;
this.errorType = this.name;
}
RequestEntityTooLargeError.prototype = Object.create(Error.prototype);
RequestEntityTooLargeError.prototype.name = 'RequestEntityTooLargeError';
module.exports = RequestEntityTooLargeError;

View file

@ -1,18 +0,0 @@
// # Theme Validation Error
// Custom error class with status code and type prefilled.
function ThemeValidationError(message, details) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 422;
if (details) {
this.errorDetails = details;
}
this.errorType = this.name;
}
ThemeValidationError.prototype = Object.create(Error.prototype);
ThemeValidationError.prototype.name = 'ThemeValidationError';
module.exports = ThemeValidationError;

View file

@ -1,14 +0,0 @@
// # Token Revocation ERror
// Custom error class with status code and type prefilled.
function TokenRevocationError(message) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 503;
this.errorType = this.name;
}
TokenRevocationError.prototype = Object.create(Error.prototype);
TokenRevocationError.prototype.name = 'TokenRevocationError';
module.exports = TokenRevocationError;

View file

@ -1,16 +0,0 @@
// # Too Many Requests Error
// Custom error class with status code and type prefilled.
function TooManyRequestsError(message, context, help) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 429;
this.errorType = this.name;
this.context = context;
this.help = help;
}
TooManyRequestsError.prototype = Object.create(Error.prototype);
TooManyRequestsError.prototype.name = 'TooManyRequestsError';
module.exports = TooManyRequestsError;

View file

@ -1,16 +0,0 @@
// # Unauthorized error
// Custom error class with status code and type prefilled.
function UnauthorizedError(message, context, help) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 401;
this.errorType = this.name;
this.context = context;
this.help = help;
}
UnauthorizedError.prototype = Object.create(Error.prototype);
UnauthorizedError.prototype.name = 'UnauthorizedError';
module.exports = UnauthorizedError;

View file

@ -1,14 +0,0 @@
// # Unsupported Media Type
// Custom error class with status code and type prefilled.
function UnsupportedMediaTypeError(message) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 415;
this.errorType = this.name;
}
UnsupportedMediaTypeError.prototype = Object.create(Error.prototype);
UnsupportedMediaTypeError.prototype.name = 'UnsupportedMediaTypeError';
module.exports = UnsupportedMediaTypeError;

View file

@ -1,19 +0,0 @@
// # Validation Error
// Custom error class with status code and type prefilled.
function ValidationError(message, offendingProperty, context, help) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 422;
if (offendingProperty) {
this.property = offendingProperty;
}
this.errorType = this.name;
this.context = context;
this.help = help;
}
ValidationError.prototype = Object.create(Error.prototype);
ValidationError.prototype.name = 'ValidationError';
module.exports = ValidationError;

View file

@ -1,14 +0,0 @@
// # Version mismatch error
// Custom error class with status code and type prefilled.
function VersionMismatchError(message) {
this.message = message;
this.stack = new Error().stack;
this.statusCode = 400;
this.errorType = this.name;
}
VersionMismatchError.prototype = Object.create(Error.prototype);
VersionMismatchError.prototype.name = 'VersionMismatchError';
module.exports = VersionMismatchError;

View file

@ -76,17 +76,17 @@ GhostServer.prototype.start = function (externalApp) {
self.httpServer.on('error', function (error) {
if (error.errno === 'EADDRINUSE') {
logging.error(new errors.InternalServerError(
i18n.t('errors.httpServer.addressInUse.error'),
i18n.t('errors.httpServer.addressInUse.context', {port: config.get('server').port}),
i18n.t('errors.httpServer.addressInUse.help')
));
logging.error(new errors.GhostError({
message: i18n.t('errors.httpServer.addressInUse.error'),
context: i18n.t('errors.httpServer.addressInUse.context', {port: config.get('server').port}),
help: i18n.t('errors.httpServer.addressInUse.help')
}));
} else {
logging.error(new errors.InternalServerError(
i18n.t('errors.httpServer.otherError.error', {errorNumber: error.errno}),
i18n.t('errors.httpServer.otherError.context'),
i18n.t('errors.httpServer.otherError.help')
));
logging.error(new errors.GhostError({
message: i18n.t('errors.httpServer.otherError.error', {errorNumber: error.errno}),
context: i18n.t('errors.httpServer.otherError.context'),
help: i18n.t('errors.httpServer.otherError.help')
}));
}
process.exit(-1);

View file

@ -4,6 +4,7 @@
var _ = require('lodash'),
hbs = require('express-hbs'),
Promise = require('bluebird'),
errors = require('../errors'),
logging = require('../logging'),
api = require('../api'),
jsonpath = require('jsonpath'),
@ -144,17 +145,18 @@ get = function get(resource, options) {
};
module.exports = function getWithLabs(resource, options) {
var self = this,
err;
var self = this, err;
if (labs.isSet('publicAPI') === true) {
// get helper is active
return get.call(self, resource, options);
} else {
err = new Error();
err.message = i18n.t('warnings.helpers.get.helperNotAvailable');
err.context = i18n.t('warnings.helpers.get.apiMustBeEnabled');
err.help = i18n.t('warnings.helpers.get.seeLink', {url: 'http://support.ghost.org/public-api-beta'});
err = new errors.GhostError({
message: i18n.t('warnings.helpers.get.helperNotAvailable'),
context: i18n.t('warnings.helpers.get.apiMustBeEnabled'),
help: i18n.t('warnings.helpers.get.seeLink', {url: 'http://support.ghost.org/public-api-beta'})
});
logging.error(err);
return Promise.resolve(function noGetHelper() {

View file

@ -1,23 +1,23 @@
var hbs = require('express-hbs'),
Promise = require('bluebird'),
errors = require('../errors'),
logging = require('../logging'),
utils = require('./utils'),
i18n = require('../i18n'),
coreHelpers = {},
var hbs = require('express-hbs'),
Promise = require('bluebird'),
errors = require('../errors'),
logging = require('../logging'),
utils = require('./utils'),
i18n = require('../i18n'),
coreHelpers = {},
registerHelpers;
if (!utils.isProduction) {
hbs.handlebars.logger.level = 0;
}
coreHelpers.asset = require('./asset');
coreHelpers.author = require('./author');
coreHelpers.body_class = require('./body_class');
coreHelpers.content = require('./content');
coreHelpers.date = require('./date');
coreHelpers.encode = require('./encode');
coreHelpers.excerpt = require('./excerpt');
coreHelpers.asset = require('./asset');
coreHelpers.author = require('./author');
coreHelpers.body_class = require('./body_class');
coreHelpers.content = require('./content');
coreHelpers.date = require('./date');
coreHelpers.encode = require('./encode');
coreHelpers.excerpt = require('./excerpt');
coreHelpers.facebook_url = require('./facebook_url');
coreHelpers.foreach = require('./foreach');
coreHelpers.get = require('./get');
@ -48,7 +48,9 @@ coreHelpers.helperMissing = function (arg) {
return undefined;
}
logging.error(new errors.InternalServerError(i18n.t('warnings.helpers.index.missingHelper', {arg: arg})));
logging.error(new errors.GhostError({
message: i18n.t('warnings.helpers.index.missingHelper', {arg: arg})
}));
};
// Register an async handlebars helper for a given handlebars instance
@ -64,8 +66,10 @@ function registerAsyncHelper(hbs, name, fn) {
Promise.resolve(fn.call(this, context, options)).then(function (result) {
cb(result);
}).catch(function (err) {
logging.warn('registerAsyncThemeHelper: ' + name);
throw err;
throw new errors.IncorrectUsageError({
err: err,
context: 'registerAsyncThemeHelper: ' + name
});
});
});
}

View file

@ -2,11 +2,11 @@
// `{{navigation}}`
// Outputs navigation menu of static urls
var _ = require('lodash'),
hbs = require('express-hbs'),
i18n = require('../i18n'),
errors = require('../errors'),
template = require('./template'),
var _ = require('lodash'),
hbs = require('express-hbs'),
i18n = require('../i18n'),
errors = require('../errors'),
template = require('./template'),
navigation;
navigation = function (options) {
@ -18,13 +18,17 @@ navigation = function (options) {
data;
if (!_.isObject(navigationData) || _.isFunction(navigationData)) {
throw new errors.IncorrectUsage(i18n.t('warnings.helpers.navigation.invalidData'));
throw new errors.IncorrectUsageError({
message: i18n.t('warnings.helpers.navigation.invalidData')
});
}
if (navigationData.filter(function (e) {
return (_.isUndefined(e.label) || _.isUndefined(e.url));
}).length > 0) {
throw new errors.IncorrectUsage(i18n.t('warnings.helpers.navigation.valuesMustBeDefined'));
throw new errors.IncorrectUsageError({
message: i18n.t('warnings.helpers.navigation.valuesMustBeDefined')
});
}
// check for non-null string values
@ -32,7 +36,9 @@ navigation = function (options) {
return ((!_.isNull(e.label) && !_.isString(e.label)) ||
(!_.isNull(e.url) && !_.isString(e.url)));
}).length > 0) {
throw new errors.IncorrectUsage(i18n.t('warnings.helpers.navigation.valuesMustBeString'));
throw new errors.IncorrectUsageError({
message: i18n.t('warnings.helpers.navigation.valuesMustBeString')
});
}
function _slugify(label) {

View file

@ -4,29 +4,35 @@
var _ = require('lodash'),
errors = require('../errors'),
template = require('./template'),
i18n = require('../i18n'),
template = require('./template'),
pagination;
pagination = function (options) {
/*jshint unused:false*/
if (!_.isObject(this.pagination) || _.isFunction(this.pagination)) {
throw new errors.IncorrectUsage(i18n.t('warnings.helpers.pagination.invalidData'));
throw new errors.IncorrectUsageError({
message: i18n.t('warnings.helpers.pagination.invalidData')
});
}
if (_.isUndefined(this.pagination.page) || _.isUndefined(this.pagination.pages) ||
_.isUndefined(this.pagination.total) || _.isUndefined(this.pagination.limit)) {
throw new errors.IncorrectUsage(i18n.t('warnings.helpers.pagination.valuesMustBeDefined'));
throw new errors.IncorrectUsageError({
message: i18n.t('warnings.helpers.pagination.valuesMustBeDefined')
});
}
if ((!_.isNull(this.pagination.next) && !_.isNumber(this.pagination.next)) ||
(!_.isNull(this.pagination.prev) && !_.isNumber(this.pagination.prev))) {
throw new errors.IncorrectUsage(i18n.t('warnings.helpers.pagination.nextPrevValuesMustBeNumeric'));
throw new errors.IncorrectUsageError({
message: i18n.t('warnings.helpers.pagination.nextPrevValuesMustBeNumeric')
});
}
if (!_.isNumber(this.pagination.page) || !_.isNumber(this.pagination.pages) ||
!_.isNumber(this.pagination.total) || !_.isNumber(this.pagination.limit)) {
throw new errors.IncorrectUsage(i18n.t('warnings.helpers.pagination.valuesMustBeNumeric'));
throw new errors.IncorrectUsageError({message: i18n.t('warnings.helpers.pagination.valuesMustBeNumeric')});
}
var data = _.merge({}, this.pagination);

View file

@ -10,14 +10,16 @@
var hbs = require('express-hbs'),
errors = require('../errors'),
_ = require('lodash'),
i18n = require('../i18n'),
_ = require('lodash'),
plural;
plural = function (number, options) {
if (_.isUndefined(options.hash) || _.isUndefined(options.hash.empty) ||
_.isUndefined(options.hash.singular) || _.isUndefined(options.hash.plural)) {
throw new errors.IncorrectUsage(i18n.t('warnings.helpers.plural.valuesMustBeDefined'));
throw new errors.IncorrectUsageError({
message: i18n.t('warnings.helpers.plural.valuesMustBeDefined')
});
}
if (number === 0) {

View file

@ -11,7 +11,9 @@ templates.execute = function (name, context, options) {
var partial = hbs.handlebars.partials[name];
if (partial === undefined) {
throw new errors.IncorrectUsage(i18n.t('warnings.helpers.template.templateNotFound', {name: name}));
throw new errors.IncorrectUsageError({
message: i18n.t('warnings.helpers.template.templateNotFound', {name: name})
});
}
// If the partial view is not compiled, it compiles and saves in handlebars

View file

@ -108,8 +108,10 @@ function init(options) {
);
}).then(function () {
debug('Apps, XMLRPC, Slack done');
// Get reference to an express app instance.
parentApp = express();
// ## Middleware and Routing
middleware(parentApp);
debug('Express done');

View file

@ -6,12 +6,9 @@ function checkVersionMatch(req, res, next) {
currentVersion = res.locals.safeVersion;
if (requestVersion && requestVersion !== currentVersion) {
return next(new errors.VersionMismatchError(
i18n.t(
'errors.middleware.api.versionMismatch',
{requestVersion: requestVersion, currentVersion: currentVersion}
)
));
return next(new errors.VersionMismatchError({
message: i18n.t('errors.middleware.api.versionMismatch', {requestVersion: requestVersion, currentVersion: currentVersion})
}));
}
next();

View file

@ -2,6 +2,7 @@ var _ = require('lodash'),
path = require('path'),
hbs = require('express-hbs'),
config = require('../config'),
errors = require('../errors'),
i18n = require('../i18n'),
_private = {};
@ -88,6 +89,12 @@ module.exports = function errorHandler(err, req, res, next) {
err = err[0];
}
if (!(err instanceof errors.GhostError)) {
err = new errors.GhostError({
err: err
});
}
req.err = err;
res.statusCode = err.statusCode;

View file

@ -1,21 +1,22 @@
var debug = require('debug')('ghost:middleware'),
bodyParser = require('body-parser'),
compress = require('compression'),
config = require('../config'),
errors = require('../errors'),
express = require('express'),
hbs = require('express-hbs'),
path = require('path'),
routes = require('../routes'),
netjet = require('netjet'),
multer = require('multer'),
tmpdir = require('os').tmpdir,
serveStatic = require('express').static,
slashes = require('connect-slashes'),
routes = require('../routes'),
config = require('../config'),
storage = require('../storage'),
logging = require('../logging'),
errors = require('../errors'),
i18n = require('../i18n'),
utils = require('../utils'),
sitemapHandler = require('../data/xml/sitemap/handler'),
multer = require('multer'),
tmpdir = require('os').tmpdir,
cacheControl = require('./cache-control'),
checkSSL = require('./check-ssl'),
decideIsAdmin = require('./decide-is-admin'),
@ -30,7 +31,6 @@ var debug = require('debug')('ghost:middleware'),
versionMatch = require('./api/version-match'),
cors = require('./cors'),
validation = require('./validation'),
netjet = require('netjet'),
labs = require('./labs'),
helpers = require('../helpers'),
middleware,
@ -52,6 +52,7 @@ middleware = {
setupMiddleware = function setupMiddleware(blogApp) {
debug('Middleware start');
var corePath = config.get('paths').corePath,
adminApp = express(),
adminHbs = hbs.create();
@ -123,9 +124,11 @@ setupMiddleware = function setupMiddleware(blogApp) {
path.join(corePath, '/shared'),
{maxAge: utils.ONE_HOUR_MS, fallthrough: false}
));
blogApp.use('/content/images', storage.getStorage().serve());
debug('Static content done');
// First determine whether we're serving admin or theme content
blogApp.use(decideIsAdmin);
blogApp.use(themeHandler.updateActiveTheme);
@ -211,7 +214,7 @@ setupMiddleware = function setupMiddleware(blogApp) {
// ### Error handlers
blogApp.use(function pageNotFound(req, res, next) {
next(new errors.NotFoundError(i18n.t('errors.errors.pageNotFound')));
next(new errors.NotFoundError({message: i18n.t('errors.errors.pageNotFound')}));
});
blogApp.use(errorHandler);

View file

@ -4,9 +4,7 @@ var config = require('../config'),
module.exports = function maintenance(req, res, next) {
if (config.get('maintenance').enabled) {
return next(new errors.Maintenance(
i18n.t('errors.general.maintenance')
));
return next(new errors.MaintenanceError({message: i18n.t('errors.general.maintenance')}));
}
next();

View file

@ -30,7 +30,7 @@ spamPrevention = {
} else if (req.body.grant_type === 'refresh_token') {
return next();
} else {
return next(new errors.BadRequestError(i18n.t('errors.middleware.spamprevention.noUsername')));
return next(new errors.BadRequestError({message: i18n.t('errors.middleware.spamprevention.noUsername')}));
}
// filter entries that are older than rateSigninPeriod
@ -43,11 +43,11 @@ spamPrevention = {
deniedRateLimit = (ipCount[remoteAddress] > rateSigninAttempts);
if (deniedRateLimit) {
return next(new errors.TooManyRequestsError(
i18n.t('errors.middleware.spamprevention.tooManyAttempts') + rateSigninPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater'),
i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.error', {rateSigninAttempts: rateSigninAttempts, rateSigninPeriod: rateSigninPeriod}),
i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.context')
));
return next(new errors.TooManyRequestsError({
message: i18n.t('errors.middleware.spamprevention.tooManyAttempts') + rateSigninPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater'),
context: i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.error', {rateSigninAttempts: rateSigninAttempts, rateSigninPeriod: rateSigninPeriod}),
help: i18n.t('errors.middleware.spamprevention.tooManySigninAttempts.context')
}));
}
next();
},
@ -74,7 +74,7 @@ spamPrevention = {
forgottenSecurity.push({ip: remoteAddress, time: currentTime, email: email, count: 0});
}
} else {
return next(new errors.BadRequestError(i18n.t('errors.middleware.spamprevention.noEmail')));
return next(new errors.BadRequestError({message: i18n.t('errors.middleware.spamprevention.noEmail')}));
}
// filter entries that are older than rateForgottenPeriod
@ -91,19 +91,22 @@ spamPrevention = {
}
if (deniedEmailRateLimit) {
return next(new errors.TooManyRequestsError(
i18n.t('errors.middleware.spamprevention.tooManyAttempts') + rateForgottenPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater'),
i18n.t('errors.middleware.spamprevention.forgottenPasswordEmail.error', {rfa: rateForgottenAttempts, rfp: rateForgottenPeriod}),
i18n.t('errors.middleware.spamprevention.forgottenPasswordEmail.context')
));
return next(new errors.TooManyRequestsError({
message: i18n.t('errors.middleware.spamprevention.tooManyAttempts') + rateForgottenPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater'),
context: i18n.t('errors.middleware.spamprevention.forgottenPasswordEmail.error', {
rfa: rateForgottenAttempts,
rfp: rateForgottenPeriod
}),
help: i18n.t('errors.middleware.spamprevention.forgottenPasswordEmail.context')
}));
}
if (deniedRateLimit) {
return next(new errors.TooManyRequestsError(
i18n.t('errors.middleware.spamprevention.tooManyAttempts') + rateForgottenPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater'),
i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error', {rfa: rateForgottenAttempts, rfp: rateForgottenPeriod}),
i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.context')
));
return next(new errors.TooManyRequestsError({
message: i18n.t('errors.middleware.spamprevention.tooManyAttempts') + rateForgottenPeriod === 3600 ? i18n.t('errors.middleware.spamprevention.waitOneHour') : i18n.t('errors.middleware.spamprevention.tryAgainLater'),
context: i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.error', {rfa: rateForgottenAttempts, rfp: rateForgottenPeriod}),
help: i18n.t('errors.middleware.spamprevention.forgottenPasswordIp.context')
}));
}
next();

View file

@ -6,7 +6,7 @@ var _ = require('lodash'),
config = require('../config'),
logging = require('../logging'),
errors = require('../errors'),
i18n = require('../i18n'),
i18n = require('../i18n'),
themeHandler;
themeHandler = {
@ -100,7 +100,9 @@ themeHandler = {
// Change theme
if (!config.get('paths').availableThemes.hasOwnProperty(activeTheme.value)) {
if (!res.isAdmin) {
return next(new errors.InternalServerError(i18n.t('errors.middleware.themehandler.missingTheme', {theme: activeTheme.value})));
return next(new errors.NotFoundError({
message: i18n.t('errors.middleware.themehandler.missingTheme', {theme: activeTheme.value})
}));
} else {
// At this point the activated theme is not present and the current
// request is for the admin client. In order to allow the user access
@ -108,7 +110,6 @@ themeHandler = {
// processing can continue.
blogApp.engine('hbs', hbs.express3());
logging.warn(i18n.t('errors.middleware.themehandler.missingTheme', {theme: activeTheme.value}));
return next();
}
} else {

View file

@ -17,12 +17,12 @@ module.exports = function upload(options) {
// Check if a file was provided
if (!apiUtils.checkFileExists(req.file)) {
return next(new errors.NoPermissionError(i18n.t('errors.api.' + type + '.missingFile')));
return next(new errors.NoPermissionError({message: i18n.t('errors.api.' + type + '.missingFile')}));
}
// Check if the file is valid
if (!apiUtils.checkFileIsValid(req.file, contentTypes, extensions)) {
return next(new errors.UnsupportedMediaTypeError(i18n.t('errors.api.' + type + '.invalidFile', {extensions: extensions})));
return next(new errors.UnsupportedMediaTypeError({message: i18n.t('errors.api.' + type + '.invalidFile', {extensions: extensions})}));
}
next();

View file

@ -173,7 +173,10 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
} else if (options.context && options.context.external) {
return 0;
} else {
throw new errors.IncorrectUsage(i18n.t('errors.models.base.index.missingContext'));
throw new errors.NotFoundError({
message: i18n.t('errors.models.base.index.missingContext'),
level: 'critical'
});
}
},

View file

@ -1,6 +1,7 @@
var config = require('../../config'),
events = require(config.get('paths:corePath') + '/server/events'),
models = require(config.get('paths:corePath') + '/server/models'),
errors = require(config.get('paths:corePath') + '/server/errors'),
logging = require(config.get('paths:corePath') + '/server/logging'),
sequence = require(config.get('paths:corePath') + '/server/utils/sequence'),
moment = require('moment-timezone');
@ -11,7 +12,7 @@ var config = require('../../config'),
events.on('token.added', function (tokenModel) {
models.User.edit({last_login: moment().toDate()}, {id: tokenModel.get('user_id')})
.catch(function (err) {
logging.error(err);
logging.error(new errors.GhostError({err: err, level: 'critical'}));
});
});
@ -61,11 +62,16 @@ events.on('settings.activeTimezone.edited', function (settingModel) {
};
})).each(function (result) {
if (!result.isFulfilled()) {
logging.error(result.reason());
logging.error(new errors.GhostError({
err: result.reason()
}));
}
});
})
.catch(function (err) {
logging.error(err);
logging.error(new errors.GhostError({
err: err,
level: 'critical'
}));
});
});

View file

@ -57,7 +57,7 @@ Basetoken = ghostBookshelf.Model.extend({
});
}
return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.base.token.noUserFound')));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.models.base.token.noUserFound')}));
},
/**

View file

@ -24,13 +24,13 @@ filterUtils = {
custom = _.map(custom, function (arg) {
return _.isString(arg) ? gql.parse(arg) : arg;
});
} catch (error) {
throw new errors.ValidationError(
error.message,
'filter',
i18n.t('errors.models.plugins.filter.errorParsing'),
i18n.t('errors.models.plugins.filter.forInformationRead', {url: 'http://api.ghost.org/docs/filter'})
);
} catch (err) {
throw new errors.ValidationError({
err: err,
property: 'filter',
context: i18n.t('errors.models.plugins.filter.errorParsing'),
help: i18n.t('errors.models.plugins.filter.forInformationRead', {url: 'http://api.ghost.org/docs/filter'})
});
}
// Merge custom filter options into a single set of statements

View file

@ -5,7 +5,6 @@ var _ = require('lodash'),
Promise = require('bluebird'),
sequence = require('../utils/sequence'),
errors = require('../errors'),
logging = require('../logging'),
Showdown = require('showdown-ghost'),
legacyConverter = new Showdown.converter({extensions: ['ghostgfm', 'footnotes', 'highlight']}),
Mobiledoc = require('mobiledoc-html-renderer').default,
@ -165,28 +164,28 @@ Post = ghostBookshelf.Model.extend({
// CASE: disallow published -> scheduled
// @TODO: remove when we have versioning based on updated_at
if (newStatus !== olderStatus && newStatus === 'scheduled' && olderStatus === 'published') {
return Promise.reject(new errors.ValidationError(
i18n.t('errors.models.post.isAlreadyPublished', {key: 'status'})
));
return Promise.reject(new errors.ValidationError({
message: i18n.t('errors.models.post.isAlreadyPublished', {key: 'status'})
}));
}
// CASE: both page and post can get scheduled
if (newStatus === 'scheduled') {
if (!publishedAt) {
return Promise.reject(new errors.ValidationError(
i18n.t('errors.models.post.valueCannotBeBlank', {key: 'published_at'})
));
return Promise.reject(new errors.ValidationError({
message: i18n.t('errors.models.post.valueCannotBeBlank', {key: 'published_at'})
}));
} else if (!moment(publishedAt).isValid()) {
return Promise.reject(new errors.ValidationError(
i18n.t('errors.models.post.valueCannotBeBlank', {key: 'published_at'})
));
return Promise.reject(new errors.ValidationError({
message: i18n.t('errors.models.post.valueCannotBeBlank', {key: 'published_at'})
}));
// CASE: to schedule/reschedule a post, a minimum diff of x minutes is needed (default configured is 2minutes)
} else if (publishedAtHasChanged && moment(publishedAt).isBefore(moment().add(config.get('times').cannotScheduleAPostBeforeInMinutes, 'minutes'))) {
return Promise.reject(new errors.ValidationError(
i18n.t('errors.models.post.expectedPublishedAtInFuture', {
return Promise.reject(new errors.ValidationError({
message: i18n.t('errors.models.post.expectedPublishedAtInFuture', {
cannotScheduleAPostBeforeInMinutes: config.get('times').cannotScheduleAPostBeforeInMinutes
})
));
}));
}
}
@ -376,16 +375,12 @@ Post = ghostBookshelf.Model.extend({
return doTagUpdates(options);
}).then(function () {
// Don't do anything, the transaction processed ok
}).catch(function failure(error) {
logging.error(new errors.InternalServerError(
error.message,
i18n.t('errors.models.post.tagUpdates.error'),
i18n.t('errors.models.post.tagUpdates.help')
));
return Promise.reject(new errors.InternalServerError(
i18n.t('errors.models.post.tagUpdates.error') + ' ' + i18n.t('errors.models.post.tagUpdates.help') + error
));
}).catch(function failure(err) {
return Promise.reject(new errors.GhostError({
err: err,
context: i18n.t('errors.models.post.tagUpdates.error'),
help: i18n.t('errors.models.post.tagUpdates.help')
}));
});
}
},
@ -673,15 +668,15 @@ Post = ghostBookshelf.Model.extend({
options = this.filterOptions(options, 'destroyByAuthor');
if (!authorId) {
throw new errors.NotFoundError(i18n.t('errors.models.post.noUserFound'));
throw new errors.NotFoundError({message: i18n.t('errors.models.post.noUserFound')});
}
return postCollection
.query('where', 'author_id', '=', authorId)
.fetch(options)
.call('invokeThen', 'destroy', options)
.catch(function (error) {
throw new errors.InternalServerError(error.message || error);
.catch(function (err) {
return Promise.reject(new errors.GhostError({err: err}));
});
}),
@ -714,7 +709,7 @@ Post = ghostBookshelf.Model.extend({
return Promise.resolve();
}
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.models.post.notEnoughPermission')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.models.post.notEnoughPermission')}));
}
});

View file

@ -78,7 +78,7 @@ Role = ghostBookshelf.Model.extend({
return Promise.resolve();
}
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.models.role.notEnoughPermission')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.models.role.notEnoughPermission')}));
}
});

View file

@ -114,7 +114,7 @@ Settings = ghostBookshelf.Model.extend({
// Accept an array of models as input
if (item.toJSON) { item = item.toJSON(); }
if (!(_.isString(item.key) && item.key.length > 0)) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.settings.valueCannotBeBlank')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.models.settings.valueCannotBeBlank')}));
}
item = self.filterData(item);
@ -138,14 +138,14 @@ Settings = ghostBookshelf.Model.extend({
return setting.save(saveData, options);
}
return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.settings.unableToFindSetting', {key: item.key})));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.models.settings.unableToFindSetting', {key: item.key})}));
});
});
},
populateDefault: function (key) {
if (!getDefaultSettings()[key]) {
return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.settings.unableToFindDefaultSetting', {key: key})));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.models.settings.unableToFindDefaultSetting', {key: key})}));
}
return this.findOne({key: key}).then(function then(foundSetting) {

View file

@ -72,7 +72,7 @@ Subscriber = ghostBookshelf.Model.extend({
return Promise.resolve();
}
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.models.subscriber.notEnoughPermission')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.models.subscriber.notEnoughPermission')}));
},
// TODO: This is a copy paste of models/user.js!

View file

@ -1,15 +1,15 @@
var _ = require('lodash'),
Promise = require('bluebird'),
errors = require('../errors'),
utils = require('../utils'),
gravatar = require('../utils/gravatar'),
bcrypt = require('bcryptjs'),
ghostBookshelf = require('./base'),
crypto = require('crypto'),
validator = require('validator'),
ghostBookshelf = require('./base'),
errors = require('../errors'),
logging = require('../logging'),
utils = require('../utils'),
gravatar = require('../utils/gravatar'),
validation = require('../data/validation'),
events = require('../events'),
logging = require('../logging'),
i18n = require('../i18n'),
bcryptGenSalt = Promise.promisify(bcrypt.genSalt),
@ -122,7 +122,9 @@ User = ghostBookshelf.Model.extend({
} else if (this.get('id')) {
return this.get('id');
} else {
throw new errors.IncorrectUsage(i18n.t('errors.models.user.missingContext'));
throw new errors.NotFoundError({
message: i18n.t('errors.models.user.missingContext')
});
}
},
@ -312,7 +314,7 @@ User = ghostBookshelf.Model.extend({
if (data.roles && data.roles.length > 1) {
return Promise.reject(
new errors.ValidationError(i18n.t('errors.models.user.onlyOneRolePerUserSupported'))
new errors.ValidationError({message: i18n.t('errors.models.user.onlyOneRolePerUserSupported')})
);
}
@ -335,7 +337,7 @@ User = ghostBookshelf.Model.extend({
}).then(function then(roleToAssign) {
if (roleToAssign && roleToAssign.get('name') === 'Owner') {
return Promise.reject(
new errors.ValidationError(i18n.t('errors.models.user.methodDoesNotSupportOwnerRole'))
new errors.ValidationError({message: i18n.t('errors.models.user.methodDoesNotSupportOwnerRole')})
);
} else {
// assign all other roles
@ -370,11 +372,11 @@ User = ghostBookshelf.Model.extend({
// check for too many roles
if (data.roles && data.roles.length > 1) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.onlyOneRolePerUserSupported')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.models.user.onlyOneRolePerUserSupported')}));
}
if (!validatePasswordLength(userData.password)) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.passwordDoesNotComplyLength')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.models.user.passwordDoesNotComplyLength')}));
}
function getAuthorRole() {
@ -422,7 +424,7 @@ User = ghostBookshelf.Model.extend({
userData = this.filterData(data);
if (!validatePasswordLength(userData.password)) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.passwordDoesNotComplyLength')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.models.user.passwordDoesNotComplyLength')}));
}
options = this.filterOptions(options, 'setup');
@ -458,6 +460,7 @@ User = ghostBookshelf.Model.extend({
if (_.isNumber(userModelOrId) || _.isString(userModelOrId)) {
// Grab the original args without the first one
origArgs = _.toArray(arguments).slice(1);
// Get the actual user model
return this.findOne({id: userModelOrId, status: 'all'}, {include: ['roles']}).then(function then(foundUserModel) {
// Build up the original args but substitute with actual model
@ -491,7 +494,7 @@ User = ghostBookshelf.Model.extend({
if (action === 'destroy') {
// Owner cannot be deleted EVER
if (loadedPermissions.user && userModel.hasRole('Owner')) {
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.models.user.notEnoughPermission')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.models.user.notEnoughPermission')}));
}
// Users with the role 'Editor' have complex permissions when the action === 'destroy'
@ -508,7 +511,7 @@ User = ghostBookshelf.Model.extend({
return Promise.resolve();
}
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.models.user.notEnoughPermission')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.models.user.notEnoughPermission')}));
},
setWarning: function setWarning(user, options) {
@ -538,7 +541,7 @@ User = ghostBookshelf.Model.extend({
s;
return this.getByEmail(object.email).then(function then(user) {
if (!user) {
return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.user.noUserWithEnteredEmailAddr')));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.models.user.noUserWithEnteredEmailAddr')}));
}
if (user.get('status') !== 'locked') {
@ -547,22 +550,19 @@ User = ghostBookshelf.Model.extend({
return Promise.resolve(self.setWarning(user, {validate: false})).then(function then(remaining) {
if (remaining === 0) {
// If remaining attempts = 0, the account has been locked, so show a locked account message
return Promise.reject(new errors.NoPermissionError(
i18n.t('errors.models.user.accountLocked')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.models.user.accountLocked')}));
}
s = (remaining > 1) ? 's' : '';
return Promise.reject(new errors.UnauthorizedError(i18n.t('errors.models.user.incorrectPasswordAttempts', {remaining: remaining, s: s})));
return Promise.reject(new errors.UnauthorizedError({message: i18n.t('errors.models.user.incorrectPasswordAttempts', {remaining: remaining, s: s})}));
// Use comma structure, not .catch, because we don't want to catch incorrect passwords
}, function handleError(err) {
// If we get a validation or other error during this save, catch it and log it, but don't
// cause a login error because of it. The user validation is not important here.
err.context = i18n.t('errors.models.user.userUpdateError.context');
err.help = i18n.t('errors.models.user.userUpdateError.help');
logging.error(err);
return Promise.reject(new errors.UnauthorizedError(i18n.t('errors.models.user.incorrectPassword')));
return Promise.reject(new errors.UnauthorizedError({
err: err,
context: i18n.t('errors.models.user.incorrectPassword'),
help: i18n.t('errors.models.user.userUpdateError.help')
}));
});
}
@ -570,19 +570,20 @@ User = ghostBookshelf.Model.extend({
.catch(function handleError(err) {
// If we get a validation or other error during this save, catch it and log it, but don't
// cause a login error because of it. The user validation is not important here.
err.context = i18n.t('errors.models.user.userUpdateError.context');
err.help = i18n.t('errors.models.user.userUpdateError.help');
logging.error(err);
logging.error(new errors.GhostError({
err: err,
context: i18n.t('errors.models.user.userUpdateError.context'),
help: i18n.t('errors.models.user.userUpdateError.help')
}));
return user;
});
});
}
return Promise.reject(new errors.NoPermissionError(
i18n.t('errors.models.user.accountLocked')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.models.user.accountLocked')}));
}, function handleError(error) {
if (error.message === 'NotFound' || error.message === 'EmptyResponse') {
return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.user.noUserWithEnteredEmailAddr')));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.models.user.noUserWithEnteredEmailAddr')}));
}
return Promise.reject(error);
@ -604,17 +605,17 @@ User = ghostBookshelf.Model.extend({
// If the two passwords do not match
if (newPassword !== ne2Password) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.newPasswordsDoNotMatch')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.models.user.newPasswordsDoNotMatch')}));
}
// If the old password is empty when changing current user's password
if (userId === options.context.user && _.isEmpty(oldPassword)) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.passwordRequiredForOperation')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.models.user.passwordRequiredForOperation')}));
}
// If password is not complex enough
if (!validatePasswordLength(newPassword)) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.passwordDoesNotComplyLength')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.models.user.passwordDoesNotComplyLength')}));
}
return self.forge({id: userId}).fetch({require: true}).then(function then(_user) {
@ -627,7 +628,7 @@ User = ghostBookshelf.Model.extend({
return true;
}).then(function then(matched) {
if (!matched) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.incorrectPassword')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.models.user.incorrectPassword')}));
}
return generatePasswordHash(newPassword);
@ -639,7 +640,7 @@ User = ghostBookshelf.Model.extend({
generateResetToken: function generateResetToken(email, expires, dbHash) {
return this.getByEmail(email).then(function then(foundUser) {
if (!foundUser) {
return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.user.noUserWithEnteredEmailAddr')));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.models.user.noUserWithEnteredEmailAddr')}));
}
var hash = crypto.createHash('sha256'),
@ -669,25 +670,25 @@ User = ghostBookshelf.Model.extend({
// Check if invalid structure
if (!parts || parts.length !== 3) {
return Promise.reject(new errors.BadRequestError(i18n.t('errors.models.user.invalidTokenStructure')));
return Promise.reject(new errors.BadRequestError({message: i18n.t('errors.models.user.invalidTokenStructure')}));
}
expires = parseInt(parts[0], 10);
email = parts[1];
if (isNaN(expires)) {
return Promise.reject(new errors.BadRequestError(i18n.t('errors.models.user.invalidTokenExpiration')));
return Promise.reject(new errors.BadRequestError({message: i18n.t('errors.models.user.invalidTokenExpiration')}));
}
// Check if token is expired to prevent replay attacks
if (expires < Date.now()) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.expiredToken')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.models.user.expiredToken')}));
}
// to prevent brute force attempts to reset the password the combination of email+expires is only allowed for
// 10 attempts
if (tokenSecurity[email + '+' + expires] && tokenSecurity[email + '+' + expires].count >= 10) {
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.models.user.tokenLocked')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.models.user.tokenLocked')}));
}
return this.generateResetToken(email, expires, dbHash).then(function then(generatedToken) {
@ -712,7 +713,7 @@ User = ghostBookshelf.Model.extend({
tokenSecurity[email + '+' + expires] = {
count: tokenSecurity[email + '+' + expires] ? tokenSecurity[email + '+' + expires].count + 1 : 1
};
return Promise.reject(new errors.BadRequestError(i18n.t('errors.models.user.invalidToken')));
return Promise.reject(new errors.BadRequestError({message: i18n.t('errors.models.user.invalidToken')}));
});
},
@ -724,11 +725,11 @@ User = ghostBookshelf.Model.extend({
dbHash = options.dbHash;
if (newPassword !== ne2Password) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.newPasswordsDoNotMatch')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.models.user.newPasswordsDoNotMatch')}));
}
if (!validatePasswordLength(newPassword)) {
return Promise.reject(new errors.ValidationError(i18n.t('errors.models.user.passwordDoesNotComplyLength')));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.models.user.passwordDoesNotComplyLength')}));
}
// Validate the token; returns the email address from token
@ -740,7 +741,7 @@ User = ghostBookshelf.Model.extend({
);
}).then(function then(results) {
if (!results[0]) {
return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.user.userNotFound')));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.models.user.userNotFound')}));
}
// Update the user with the new password hash
@ -764,7 +765,7 @@ User = ghostBookshelf.Model.extend({
// check if user has the owner role
var currentRoles = contextUser.toJSON(options).roles;
if (!_.some(currentRoles, {id: ownerRole.id})) {
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.models.user.onlyOwnerCanTransferOwnerRole')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.models.user.onlyOwnerCanTransferOwnerRole')}));
}
return Promise.join(ghostBookshelf.model('Role').findOne({name: 'Administrator'}),
@ -775,7 +776,7 @@ User = ghostBookshelf.Model.extend({
currentRoles = user.toJSON(options).roles;
if (!_.some(currentRoles, {id: adminRole.id})) {
return Promise.reject(new errors.ValidationError('errors.models.user.onlyAdmCanBeAssignedOwnerRole'));
return Promise.reject(new errors.ValidationError({message: i18n.t('errors.models.user.onlyAdmCanBeAssignedOwnerRole')}));
}
// convert owner to admin

View file

@ -11,7 +11,7 @@ effective = {
.then(function (foundUser) {
// CASE: {context: {user: id}} where the id is not in our database
if (!foundUser) {
return Promise.reject(new errors.NotFoundError(i18n.t('errors.models.user.userNotFound')));
return Promise.reject(new errors.NotFoundError({message: i18n.t('errors.models.user.userNotFound')}));
}
var seenPerms = {},

View file

@ -56,7 +56,7 @@ function parseContext(context) {
}
function applyStatusRules(docName, method, opts) {
var err = new errors.NoPermissionError(i18n.t('errors.permissions.applyStatusRules.error', {docName: docName}));
var err = new errors.NoPermissionError({message: i18n.t('errors.permissions.applyStatusRules.error', {docName: docName})});
// Enforce status 'active' for users
if (docName === 'users') {
@ -202,7 +202,7 @@ CanThisResult.prototype.buildObjectTypeHandlers = function (objTypes, actType, c
return;
}
return Promise.reject(new errors.NoPermissionError(i18n.t('errors.permissions.noPermissionToAction')));
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.permissions.noPermissionToAction')}));
});
};

View file

@ -2,6 +2,7 @@ var util = require('util'),
moment = require('moment'),
request = require('superagent'),
SchedulingBase = require(__dirname + '/SchedulingBase'),
errors = require(__dirname + '/../errors'),
logging = require(__dirname + '/../logging');
/**
@ -212,7 +213,10 @@ SchedulingDefault.prototype._pingUrl = function (object) {
}, self.retryTimeoutInMs);
}
logging.error(err);
logging.error(new errors.GhostError({
err: err,
level: 'critical'
}));
}
});
};

View file

@ -42,11 +42,11 @@ exports.init = function init(options) {
client = null;
if (!config) {
return Promise.reject(new errors.IncorrectUsage('post-scheduling: no config was provided'));
return Promise.reject(new errors.IncorrectUsageError({message: 'post-scheduling: no config was provided'}));
}
if (!apiUrl) {
return Promise.reject(new errors.IncorrectUsage('post-scheduling: no apiUrl was provided'));
return Promise.reject(new errors.IncorrectUsageError({message: 'post-scheduling: no apiUrl was provided'}));
}
return _private.loadClient()

View file

@ -12,7 +12,7 @@ exports.createAdapter = function (options) {
contentPath = options.contentPath;
if (!activeAdapter) {
return Promise.reject(new errors.IncorrectUsage('Please provide an active adapter.'));
return Promise.reject(new errors.IncorrectUsageError({message: 'Please provide an active adapter.'}));
}
/**
@ -22,7 +22,7 @@ exports.createAdapter = function (options) {
adapter = new (require(activeAdapter))(options);
} catch (err) {
if (err.code !== 'MODULE_NOT_FOUND') {
return Promise.reject(new errors.IncorrectUsage(err.message));
return Promise.reject(new errors.IncorrectUsageError({err: err}));
}
}
@ -34,11 +34,11 @@ exports.createAdapter = function (options) {
} catch (err) {
// CASE: only throw error if module does exist
if (err.code !== 'MODULE_NOT_FOUND') {
return Promise.reject(new errors.IncorrectUsage(err.message));
return Promise.reject(new errors.IncorrectUsageError({err: err}));
}
// CASE: if module not found it can be an error within the adapter (cannot find bluebird for example)
else if (err.code === 'MODULE_NOT_FOUND' && err.message.indexOf(contentPath + activeAdapter) === -1) {
return Promise.reject(new errors.IncorrectUsage(err.message));
return Promise.reject(new errors.IncorrectUsageError({err: err}));
}
}
@ -50,22 +50,22 @@ exports.createAdapter = function (options) {
} catch (err) {
// CASE: only throw error if module does exist
if (err.code === 'MODULE_NOT_FOUND') {
return Promise.reject(new errors.IncorrectUsage('We cannot find your adapter in: ' + contentPath + ' or: ' + internalPath));
return Promise.reject(new errors.IncorrectUsageError({message: 'We cannot find your adapter in: ' + contentPath + ' or: ' + internalPath}));
}
return Promise.reject(new errors.IncorrectUsage(err.message));
return Promise.reject(new errors.IncorrectUsageError({err: err}));
}
if (!(adapter instanceof SchedulingBase)) {
return Promise.reject(new errors.IncorrectUsage('Your adapter does not inherit from the SchedulingBase.'));
return Promise.reject(new errors.IncorrectUsageError({message: 'Your adapter does not inherit from the SchedulingBase.'}));
}
if (!adapter.requiredFns) {
return Promise.reject(new errors.IncorrectUsage('Your adapter does not provide the minimum required functions.'));
return Promise.reject(new errors.IncorrectUsageError({message: 'Your adapter does not provide the minimum required functions.'}));
}
if (_.xor(adapter.requiredFns, Object.keys(_.pick(Object.getPrototypeOf(adapter), adapter.requiredFns))).length) {
return Promise.reject(new errors.IncorrectUsage('Your adapter does not provide the minimum required functions.'));
return Promise.reject(new errors.IncorrectUsageError({message: 'Your adapter does not provide the minimum required functions.'}));
}
return Promise.resolve(adapter);

View file

@ -23,7 +23,9 @@ function getStorage(type) {
// CASE: type does not exist
if (!storageChoice) {
throw new errors.IncorrectUsage('No adapter found for type: ' + type);
throw new errors.IncorrectUsageError({
message: 'No adapter found for type: ' + type
});
}
// cache?
@ -37,11 +39,11 @@ function getStorage(type) {
} catch (err) {
// CASE: only throw error if module does exist
if (err.code !== 'MODULE_NOT_FOUND') {
throw new errors.IncorrectUsage(err.message);
throw new errors.IncorrectUsageError({err: err});
}
// CASE: if module not found it can be an error within the adapter (cannot find bluebird for example)
else if (err.code === 'MODULE_NOT_FOUND' && err.message.indexOf(config.getContentPath('storage') + storageChoice) === -1) {
throw new errors.IncorrectUsage(err.message);
throw new errors.IncorrectUsageError({err: err});
}
}
@ -50,24 +52,27 @@ function getStorage(type) {
storage[storageChoice] = storage[storageChoice] || require(config.get('paths').internalStoragePath + storageChoice);
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
throw new errors.IncorrectUsage('We cannot find your adapter in: ' + config.getContentPath('storage') + ' or: ' + config.get('paths').internalStoragePath);
throw new errors.IncorrectUsageError({
err: err,
context: 'We cannot find your adapter in: ' + config.getContentPath('storage') + ' or: ' + config.get('paths').internalStoragePath
});
} else {
throw new errors.IncorrectUsage(err.message);
throw new errors.IncorrectUsageError({err: err});
}
}
storage[storageChoice] = new storage[storageChoice](storageConfig);
if (!(storage[storageChoice] instanceof Base)) {
throw new errors.IncorrectUsage('Your storage adapter does not inherit from the Storage Base.');
throw new errors.IncorrectUsageError({message: 'Your storage adapter does not inherit from the Storage Base.'});
}
if (!storage[storageChoice].requiredFns) {
throw new errors.IncorrectUsage('Your storage adapter does not provide the minimum required functions.');
throw new errors.IncorrectUsageError({message:'Your storage adapter does not provide the minimum required functions.'});
}
if (_.xor(storage[storageChoice].requiredFns, Object.keys(_.pick(Object.getPrototypeOf(storage[storageChoice]), storage[storageChoice].requiredFns))).length) {
throw new errors.IncorrectUsage('Your storage adapter does not provide the minimum required functions.');
throw new errors.IncorrectUsageError({message:'Your storage adapter does not provide the minimum required functions.'});
}
return storage[storageChoice];

View file

@ -105,10 +105,10 @@ LocalFileStore.prototype.serve = function (options) {
return serveStatic(config.getContentPath('images'), {maxAge: utils.ONE_YEAR_MS, fallthrough: false})(req, res, function (err) {
if (err) {
if (err.statusCode === 404) {
return next(new errors.NotFoundError(i18n.t('errors.errors.pageNotFound')));
return next(new errors.NotFoundError({message: i18n.t('errors.errors.pageNotFound')}));
}
return next(err);
return next(new errors.GhostError({err: err}));
}
next();

View file

@ -175,7 +175,8 @@
"general": {
"maintenance": "Ghost is currently undergoing maintenance, please wait a moment then retry.",
"moreInfo": "\nMore info: {info}",
"requiredOnFuture": "This will be required in future. Please see {link}"
"requiredOnFuture": "This will be required in future. Please see {link}",
"internalError": "Something went wrong."
},
"httpServer": {
"addressInUse": {

View file

@ -31,21 +31,24 @@ var crypto = require('crypto'),
api = require('./api'),
config = require('./config'),
logging = require('./logging'),
errors = require('./errors'),
i18n = require('./i18n'),
internal = {context: {internal: true}},
allowedCheckEnvironments = ['development', 'production'],
checkEndpoint = 'updates.ghost.org',
currentVersion = config.get('ghostVersion');
function updateCheckError(error) {
function updateCheckError(err) {
api.settings.edit(
{settings: [{key: 'nextUpdateCheck', value: Math.round(Date.now() / 1000 + 24 * 3600)}]},
internal
);
error.context = i18n.t('errors.update-check.checkingForUpdatesFailed.error');
error.help = i18n.t('errors.update-check.checkingForUpdatesFailed.help', {url: 'http://support.ghost.org'});
logging.error(error);
logging.error(new errors.GhostError({
err: err,
context: i18n.t('errors.update-check.checkingForUpdatesFailed.error'),
help: i18n.t('errors.update-check.checkingForUpdatesFailed.help', {url: 'http://support.ghost.org'})
}));
}
/**

View file

@ -2,7 +2,7 @@ var should = require('should'),
sinon = require('sinon'),
errors = require('../../../../server/errors'),
// Stuff we are testing
// Stuff we are testing
handleError = require('../../../../server/controllers/frontend/error'),
sandbox = sinon.sandbox.create();
@ -21,7 +21,7 @@ describe('handleError', function () {
});
it('should call next with no args for 404 errors', function () {
var notFoundError = new errors.NotFoundError('Something wasn\'t found');
var notFoundError = new errors.NotFoundError({message: 'Something wasn\'t found'});
handleError(next)(notFoundError);
next.calledOnce.should.be.true();
@ -29,7 +29,8 @@ describe('handleError', function () {
});
it('should call next with error for other errors', function () {
var otherError = new errors.MethodNotAllowedError('Something wasn\'t allowed');
var otherError = new Error();
otherError.message = 'Something wasn\'t allowed';
handleError(next)(otherError);

View file

@ -1,16 +1,12 @@
var should = require('should'),
sinon = require('sinon'),
Promise = require('bluebird'),
// Stuff we're testing
db = require('../../server/data/db'),
errors = require('../../server/errors'),
exporter = require('../../server/data/export'),
schema = require('../../server/data/schema'),
settings = require('../../server/api/settings'),
schemaTables = Object.keys(schema.tables),
sandbox = sinon.sandbox.create();
describe('Exporter', function () {
@ -85,12 +81,14 @@ describe('Exporter', function () {
queryMock.select.returns(new Promise.reject({}));
// Execute
exporter.doExport().then(function () {
done(new Error('expected error on export data'));
}).catch(function (err) {
(err instanceof errors.InternalServerError).should.eql(true);
done();
});
exporter.doExport()
.then(function () {
done(new Error('expected error for export'));
})
.catch(function (err) {
(err instanceof errors.DataExportError).should.eql(true);
done();
});
});
});

View file

@ -122,6 +122,7 @@ describe('Theme Handler', function () {
describe('updateActiveTheme', function () {
it('updates the active theme if changed', function (done) {
var activateThemeSpy = sandbox.spy(themeHandler, 'activateTheme');
sandbox.stub(api.settings, 'read').withArgs(sandbox.match.has('key', 'activeTheme')).returns(Promise.resolve({
settings: [{
key: 'activeKey',
@ -176,8 +177,8 @@ describe('Theme Handler', function () {
});
it('throws only warns if theme is missing for admin req', function (done) {
var warnSpy = sandbox.spy(logging, 'warn'),
activateThemeSpy = sandbox.spy(themeHandler, 'activateTheme');
var activateThemeSpy = sandbox.spy(themeHandler, 'activateTheme'),
loggingWarnStub = sandbox.spy(logging, 'warn');
sandbox.stub(api.settings, 'read').withArgs(sandbox.match.has('key', 'activeTheme')).returns(Promise.resolve({
settings: [{
@ -185,14 +186,15 @@ describe('Theme Handler', function () {
value: 'rasper'
}]
}));
res.isAdmin = true;
blogApp.set('activeTheme', 'not-casper');
configUtils.set({paths: {availableThemes: {casper: {}}}});
themeHandler.updateActiveTheme(req, res, function () {
activateThemeSpy.called.should.be.false();
warnSpy.called.should.be.true();
warnSpy.calledWith('The currently active theme "rasper" is missing.').should.be.true();
loggingWarnStub.called.should.be.true();
loggingWarnStub.calledWith('The currently active theme "rasper" is missing.').should.be.true();
done();
});
});

Some files were not shown because too many files have changed in this diff Show more