2013-05-16 15:21:13 +04:00
|
|
|
// # Ghost Data API
|
|
|
|
// Provides access to the data model
|
|
|
|
|
2013-09-24 14:46:30 +04:00
|
|
|
var Ghost = require('../ghost'),
|
|
|
|
_ = require('underscore'),
|
|
|
|
when = require('when'),
|
|
|
|
errors = require('./errorHandling'),
|
|
|
|
permissions = require('./permissions'),
|
|
|
|
canThis = permissions.canThis,
|
|
|
|
|
|
|
|
ghost = new Ghost(),
|
2013-06-25 15:43:15 +04:00
|
|
|
dataProvider = ghost.dataProvider,
|
|
|
|
posts,
|
|
|
|
users,
|
2013-08-21 16:55:58 +04:00
|
|
|
tags,
|
2013-06-27 07:52:56 +04:00
|
|
|
notifications,
|
2013-06-25 15:43:15 +04:00
|
|
|
settings,
|
|
|
|
requestHandler,
|
|
|
|
settingsObject,
|
2013-09-15 20:03:31 +04:00
|
|
|
settingsCollection,
|
|
|
|
settingsFilter;
|
2013-06-25 15:43:15 +04:00
|
|
|
|
2013-08-06 23:27:56 +04:00
|
|
|
// ## Posts
|
2013-06-25 15:43:15 +04:00
|
|
|
posts = {
|
2013-08-06 23:27:56 +04:00
|
|
|
// #### Browse
|
|
|
|
|
|
|
|
// **takes:** filter / pagination parameters
|
2013-06-25 15:43:15 +04:00
|
|
|
browse: function browse(options) {
|
2013-08-06 23:27:56 +04:00
|
|
|
// **returns:** a promise for a page of posts in a json object
|
2013-06-25 15:43:15 +04:00
|
|
|
return dataProvider.Post.findPage(options);
|
|
|
|
},
|
2013-08-06 23:27:56 +04:00
|
|
|
|
|
|
|
// #### Read
|
|
|
|
|
|
|
|
// **takes:** an identifier (id or slug?)
|
2013-06-25 15:43:15 +04:00
|
|
|
read: function read(args) {
|
2013-08-06 23:27:56 +04:00
|
|
|
// **returns:** a promise for a single post in a json object
|
2013-06-25 15:43:15 +04:00
|
|
|
return dataProvider.Post.findOne(args);
|
|
|
|
},
|
2013-08-06 23:27:56 +04:00
|
|
|
|
|
|
|
// #### Edit
|
|
|
|
|
|
|
|
// **takes:** a json object with all the properties which should be updated
|
2013-06-25 15:43:15 +04:00
|
|
|
edit: function edit(postData) {
|
2013-08-06 23:27:56 +04:00
|
|
|
// **returns:** a promise for the resulting post in a json object
|
2013-08-16 03:22:08 +04:00
|
|
|
if (!this.user) {
|
|
|
|
return when.reject("You do not have permission to edit this post.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return canThis(this.user).edit.post(postData.id).then(function () {
|
|
|
|
return dataProvider.Post.edit(postData);
|
|
|
|
}, function () {
|
|
|
|
return when.reject("You do not have permission to edit this post.");
|
|
|
|
});
|
2013-06-25 15:43:15 +04:00
|
|
|
},
|
2013-08-06 23:27:56 +04:00
|
|
|
|
|
|
|
// #### Add
|
|
|
|
|
|
|
|
// **takes:** a json object representing a post,
|
2013-06-25 15:43:15 +04:00
|
|
|
add: function add(postData) {
|
2013-08-06 23:27:56 +04:00
|
|
|
// **returns:** a promise for the resulting post in a json object
|
2013-08-16 03:22:08 +04:00
|
|
|
if (!this.user) {
|
|
|
|
return when.reject("You do not have permission to add posts.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return canThis(this.user).create.post().then(function () {
|
|
|
|
return dataProvider.Post.add(postData);
|
|
|
|
}, function () {
|
|
|
|
return when.reject("You do not have permission to add posts.");
|
|
|
|
});
|
2013-06-25 15:43:15 +04:00
|
|
|
},
|
2013-08-06 23:27:56 +04:00
|
|
|
|
|
|
|
// #### Destroy
|
|
|
|
|
|
|
|
// **takes:** an identifier (id or slug?)
|
2013-06-25 15:43:15 +04:00
|
|
|
destroy: function destroy(args) {
|
2013-08-06 23:27:56 +04:00
|
|
|
// **returns:** a promise for a json response with the id of the deleted post
|
2013-08-16 03:22:08 +04:00
|
|
|
if (!this.user) {
|
|
|
|
return when.reject("You do not have permission to remove posts.");
|
|
|
|
}
|
|
|
|
|
|
|
|
return canThis(this.user).remove.post(args.id).then(function () {
|
2013-09-24 19:21:43 +04:00
|
|
|
return when(posts.read({id : args.id})).then(function (result) {
|
|
|
|
return dataProvider.Post.destroy(args.id).then(function () {
|
|
|
|
var deletedObj = {};
|
|
|
|
deletedObj.id = result.attributes.id;
|
|
|
|
deletedObj.slug = result.attributes.slug;
|
|
|
|
return deletedObj;
|
|
|
|
});
|
|
|
|
});
|
2013-08-16 03:22:08 +04:00
|
|
|
}, function () {
|
|
|
|
return when.reject("You do not have permission to remove posts.");
|
|
|
|
});
|
2013-06-25 15:43:15 +04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-08-06 23:27:56 +04:00
|
|
|
// ## Users
|
2013-06-25 15:43:15 +04:00
|
|
|
users = {
|
2013-08-06 23:27:56 +04:00
|
|
|
// #### Browse
|
|
|
|
|
|
|
|
// **takes:** options object
|
2013-08-05 21:26:44 +04:00
|
|
|
browse: function browse(options) {
|
2013-08-06 23:27:56 +04:00
|
|
|
// **returns:** a promise for a collection of users in a json object
|
2013-08-05 21:26:44 +04:00
|
|
|
return dataProvider.User.browse(options);
|
|
|
|
},
|
2013-08-06 23:27:56 +04:00
|
|
|
|
|
|
|
// #### Read
|
|
|
|
|
|
|
|
// **takes:** an identifier (id or slug?)
|
2013-08-05 21:26:44 +04:00
|
|
|
read: function read(args) {
|
2013-08-06 23:27:56 +04:00
|
|
|
// **returns:** a promise for a single user in a json object
|
2013-08-09 05:22:49 +04:00
|
|
|
if (args.id === 'me') {
|
|
|
|
args = {id: this.user};
|
|
|
|
}
|
|
|
|
|
2013-10-14 19:34:50 +04:00
|
|
|
var filteredAttributes = ['password', 'created_by', 'updated_by'];
|
|
|
|
|
|
|
|
return dataProvider.User.read(args).then(function omitAttrs(result) {
|
|
|
|
return _.omit(result, filteredAttributes);
|
|
|
|
});
|
2013-08-05 21:26:44 +04:00
|
|
|
},
|
2013-08-06 23:27:56 +04:00
|
|
|
|
|
|
|
// #### Edit
|
|
|
|
|
|
|
|
// **takes:** a json object representing a user
|
|
|
|
edit: function edit(userData) {
|
|
|
|
// **returns:** a promise for the resulting user in a json object
|
2013-08-09 05:22:49 +04:00
|
|
|
userData.id = this.user;
|
2013-08-06 23:27:56 +04:00
|
|
|
return dataProvider.User.edit(userData);
|
2013-08-05 21:26:44 +04:00
|
|
|
},
|
2013-08-06 23:27:56 +04:00
|
|
|
|
|
|
|
// #### Add
|
|
|
|
|
|
|
|
// **takes:** a json object representing a user
|
|
|
|
add: function add(userData) {
|
|
|
|
|
|
|
|
// **returns:** a promise for the resulting user in a json object
|
|
|
|
return dataProvider.User.add(userData);
|
2013-06-25 15:43:15 +04:00
|
|
|
},
|
2013-08-06 23:27:56 +04:00
|
|
|
|
|
|
|
// #### Check
|
|
|
|
// Checks a password matches the given email address
|
|
|
|
|
|
|
|
// **takes:** a json object representing a user
|
|
|
|
check: function check(userData) {
|
|
|
|
// **returns:** on success, returns a promise for the resulting user in a json object
|
|
|
|
return dataProvider.User.check(userData);
|
2013-08-06 03:49:06 +04:00
|
|
|
},
|
2013-08-06 23:27:56 +04:00
|
|
|
|
|
|
|
// #### Change Password
|
|
|
|
|
|
|
|
// **takes:** a json object representing a user
|
|
|
|
changePassword: function changePassword(userData) {
|
|
|
|
// **returns:** on success, returns a promise for the resulting user in a json object
|
|
|
|
return dataProvider.User.changePassword(userData);
|
2013-09-01 02:20:12 +04:00
|
|
|
},
|
|
|
|
|
|
|
|
forgottenPassword: function forgottenPassword(email) {
|
|
|
|
return dataProvider.User.forgottenPassword(email);
|
2013-06-25 15:43:15 +04:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-08-21 16:55:58 +04:00
|
|
|
tags = {
|
|
|
|
// #### All
|
|
|
|
|
|
|
|
// **takes:** Nothing yet
|
|
|
|
all: function browse() {
|
|
|
|
// **returns:** a promise for all tags which have previously been used in a json object
|
|
|
|
return dataProvider.Tag.findAll();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-08-06 23:27:56 +04:00
|
|
|
// ## Notifications
|
2013-06-27 07:52:56 +04:00
|
|
|
notifications = {
|
2013-08-06 23:27:56 +04:00
|
|
|
// #### Destroy
|
|
|
|
|
|
|
|
// **takes:** an identifier (id)
|
2013-06-27 07:52:56 +04:00
|
|
|
destroy: function destroy(i) {
|
|
|
|
ghost.notifications = _.reject(ghost.notifications, function (element) {
|
|
|
|
return element.id === i.id;
|
|
|
|
});
|
2013-08-06 23:27:56 +04:00
|
|
|
// **returns:** a promise for remaining notifications as a json object
|
2013-06-27 07:52:56 +04:00
|
|
|
return when(ghost.notifications);
|
|
|
|
},
|
2013-08-06 23:27:56 +04:00
|
|
|
|
|
|
|
// #### Add
|
|
|
|
|
|
|
|
// **takes:** a notification object of the form
|
|
|
|
// ```
|
|
|
|
// msg = {
|
|
|
|
// type: 'error', // this can be 'error', 'success', 'warn' and 'info'
|
|
|
|
// message: 'This is an error', // A string. Should fit in one line.
|
|
|
|
// status: 'persistent', // or 'passive'
|
|
|
|
// id: 'auniqueid' // A unique ID
|
|
|
|
// };
|
|
|
|
// ```
|
2013-06-27 07:52:56 +04:00
|
|
|
add: function add(notification) {
|
2013-08-06 23:27:56 +04:00
|
|
|
// **returns:** a promise for all notifications as a json object
|
2013-06-27 07:52:56 +04:00
|
|
|
return when(ghost.notifications.push(notification));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-08-06 23:27:56 +04:00
|
|
|
// ## Settings
|
2013-06-25 15:43:15 +04:00
|
|
|
|
2013-08-06 23:27:56 +04:00
|
|
|
// ### Helpers
|
2013-06-25 15:43:15 +04:00
|
|
|
// Turn a settings collection into a single object/hashmap
|
|
|
|
settingsObject = function (settings) {
|
2013-09-15 20:03:31 +04:00
|
|
|
if (_.isObject(settings)) {
|
|
|
|
return _.reduce(settings, function (res, item, key) {
|
|
|
|
if (_.isArray(item)) {
|
|
|
|
res[key] = item;
|
|
|
|
} else {
|
|
|
|
res[key] = item.value;
|
|
|
|
}
|
|
|
|
return res;
|
|
|
|
}, {});
|
|
|
|
}
|
2013-06-25 15:43:15 +04:00
|
|
|
return (settings.toJSON ? settings.toJSON() : settings).reduce(function (res, item) {
|
|
|
|
if (item.toJSON) { item = item.toJSON(); }
|
|
|
|
if (item.key) { res[item.key] = item.value; }
|
|
|
|
return res;
|
|
|
|
}, {});
|
|
|
|
};
|
|
|
|
// Turn an object into a collection
|
|
|
|
settingsCollection = function (settings) {
|
|
|
|
return _.map(settings, function (value, key) {
|
|
|
|
return { key: key, value: value };
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2013-10-16 04:32:44 +04:00
|
|
|
// Filters an object based on a given filter object
|
2013-09-15 20:03:31 +04:00
|
|
|
settingsFilter = function (settings, filter) {
|
|
|
|
return _.object(_.filter(_.pairs(settings), function (setting) {
|
|
|
|
if (filter) {
|
2013-09-24 14:46:30 +04:00
|
|
|
return _.some(filter.split(','), function (f) {
|
2013-09-15 20:03:31 +04:00
|
|
|
return setting[1].type === f;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}));
|
|
|
|
};
|
|
|
|
|
2013-06-25 15:43:15 +04:00
|
|
|
settings = {
|
2013-08-06 23:27:56 +04:00
|
|
|
// #### Browse
|
|
|
|
|
|
|
|
// **takes:** options object
|
2013-06-25 15:43:15 +04:00
|
|
|
browse: function browse(options) {
|
2013-09-15 20:03:31 +04:00
|
|
|
// **returns:** a promise for a settings json object
|
|
|
|
if (ghost.settings()) {
|
|
|
|
return when(ghost.settings()).then(function (settings) {
|
|
|
|
return settingsObject(settingsFilter(settings, options.type));
|
|
|
|
}, errors.logAndThrowError);
|
|
|
|
}
|
2013-06-25 15:43:15 +04:00
|
|
|
},
|
2013-08-06 23:27:56 +04:00
|
|
|
|
|
|
|
// #### Read
|
|
|
|
|
|
|
|
// **takes:** either a json object containing a key, or a single key string
|
2013-06-25 15:43:15 +04:00
|
|
|
read: function read(options) {
|
2013-08-05 19:11:32 +04:00
|
|
|
if (_.isString(options)) {
|
|
|
|
options = { key: options };
|
|
|
|
}
|
|
|
|
|
2013-09-15 20:03:31 +04:00
|
|
|
if (ghost.settings()) {
|
|
|
|
return when(ghost.settings()[options.key]).then(function (setting) {
|
|
|
|
if (!setting) {
|
|
|
|
return when.reject("Unable to find setting: " + options.key);
|
|
|
|
}
|
|
|
|
var res = {};
|
|
|
|
res.key = options.key;
|
|
|
|
res.value = setting.value;
|
|
|
|
return res;
|
|
|
|
}, errors.logAndThrowError);
|
|
|
|
}
|
2013-06-25 15:43:15 +04:00
|
|
|
},
|
2013-08-06 23:27:56 +04:00
|
|
|
|
|
|
|
// #### Edit
|
|
|
|
|
|
|
|
// **takes:** either a json object representing a collection of settings, or a key and value pair
|
2013-08-05 19:11:32 +04:00
|
|
|
edit: function edit(key, value) {
|
|
|
|
// Check for passing a collection of settings first
|
|
|
|
if (_.isObject(key)) {
|
2013-09-15 20:03:31 +04:00
|
|
|
//clean data
|
|
|
|
var type = key.type;
|
|
|
|
delete key.type;
|
|
|
|
delete key.availableThemes;
|
2013-08-06 23:27:56 +04:00
|
|
|
|
2013-09-15 20:03:31 +04:00
|
|
|
key = settingsCollection(key);
|
|
|
|
return dataProvider.Settings.edit(key).then(function (result) {
|
|
|
|
result.models = result;
|
|
|
|
return when(ghost.readSettingsResult(result)).then(function (settings) {
|
|
|
|
ghost.updateSettingsCache(settings);
|
|
|
|
return settingsObject(settingsFilter(ghost.settings(), type));
|
|
|
|
});
|
|
|
|
}, errors.logAndThrowError);
|
2013-08-05 19:11:32 +04:00
|
|
|
}
|
|
|
|
return dataProvider.Settings.read(key).then(function (setting) {
|
|
|
|
if (!setting) {
|
|
|
|
return when.reject("Unable to find setting: " + key);
|
|
|
|
}
|
|
|
|
if (!_.isString(value)) {
|
|
|
|
value = JSON.stringify(value);
|
|
|
|
}
|
|
|
|
setting.set('value', value);
|
2013-09-15 20:03:31 +04:00
|
|
|
return dataProvider.Settings.edit(setting).then(function (result) {
|
|
|
|
ghost.settings()[_.first(result).attributes.key].value = _.first(result).attributes.value;
|
|
|
|
return settingsObject(ghost.settings());
|
|
|
|
}, errors.logAndThrowError);
|
2013-08-30 15:20:30 +04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-08-06 23:27:56 +04:00
|
|
|
// ## Request Handlers
|
2013-06-25 15:43:15 +04:00
|
|
|
|
2013-09-24 19:21:43 +04:00
|
|
|
function invalidateCache(req, res, result) {
|
|
|
|
var parsedUrl = req._parsedUrl.pathname.replace(/\/$/, '').split('/'),
|
|
|
|
method = req.method,
|
|
|
|
endpoint = parsedUrl[3],
|
|
|
|
id = parsedUrl[4],
|
|
|
|
cacheInvalidate;
|
|
|
|
if (method === 'POST' || method === 'PUT' || method === 'DELETE') {
|
|
|
|
if (endpoint === 'settings' || endpoint === 'users') {
|
|
|
|
cacheInvalidate = "/*";
|
|
|
|
} else if (endpoint === 'posts') {
|
|
|
|
cacheInvalidate = "/, /page/*, /rss/, /rss/*";
|
|
|
|
if (id) {
|
|
|
|
if (result.toJSON) {
|
2013-10-02 19:14:35 +04:00
|
|
|
cacheInvalidate += ', /' + result.toJSON().slug + '/';
|
2013-09-24 19:21:43 +04:00
|
|
|
} else {
|
2013-10-02 19:14:35 +04:00
|
|
|
cacheInvalidate += ', /' + result.slug + '/';
|
2013-09-24 19:21:43 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (cacheInvalidate) {
|
|
|
|
res.set({
|
|
|
|
"X-Cache-Invalidate": cacheInvalidate
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-06 23:27:56 +04:00
|
|
|
// ### requestHandler
|
2013-06-25 15:43:15 +04:00
|
|
|
// decorator for api functions which are called via an HTTP request
|
|
|
|
// takes the API method and wraps it so that it gets data from the request and returns a sensible JSON response
|
|
|
|
requestHandler = function (apiMethod) {
|
|
|
|
return function (req, res) {
|
2013-08-09 05:22:49 +04:00
|
|
|
var options = _.extend(req.body, req.query, req.params),
|
|
|
|
apiContext = {
|
|
|
|
user: req.session && req.session.user
|
|
|
|
};
|
|
|
|
|
|
|
|
return apiMethod.call(apiContext, options).then(function (result) {
|
2013-09-24 19:21:43 +04:00
|
|
|
invalidateCache(req, res, result);
|
2013-06-25 15:43:15 +04:00
|
|
|
res.json(result || {});
|
|
|
|
}, function (error) {
|
2013-08-20 22:52:44 +04:00
|
|
|
error = {error: _.isString(error) ? error : (_.isObject(error) ? error.message : 'Unknown API Error')};
|
|
|
|
res.json(400, error);
|
2013-06-08 09:05:40 +04:00
|
|
|
});
|
|
|
|
};
|
2013-06-25 15:43:15 +04:00
|
|
|
};
|
|
|
|
|
2013-08-06 23:27:56 +04:00
|
|
|
// Public API
|
2013-06-25 15:43:15 +04:00
|
|
|
module.exports.posts = posts;
|
|
|
|
module.exports.users = users;
|
2013-08-21 16:55:58 +04:00
|
|
|
module.exports.tags = tags;
|
2013-06-27 07:52:56 +04:00
|
|
|
module.exports.notifications = notifications;
|
2013-06-25 15:43:15 +04:00
|
|
|
module.exports.settings = settings;
|
|
|
|
module.exports.requestHandler = requestHandler;
|