2013-05-16 06:21:13 -05:00
|
|
|
// # Ghost Data API
|
|
|
|
// Provides access to the data model
|
|
|
|
|
2014-02-05 03:40:30 -05:00
|
|
|
var _ = require('lodash'),
|
2013-12-06 03:51:35 -05:00
|
|
|
when = require('when'),
|
2013-12-30 02:03:29 -05:00
|
|
|
config = require('../config'),
|
2013-12-06 03:51:35 -05:00
|
|
|
db = require('./db'),
|
|
|
|
settings = require('./settings'),
|
|
|
|
notifications = require('./notifications'),
|
|
|
|
posts = require('./posts'),
|
|
|
|
users = require('./users'),
|
|
|
|
tags = require('./tags'),
|
2014-04-03 20:59:09 -05:00
|
|
|
mail = require('./mail'),
|
2013-06-25 06:43:15 -05:00
|
|
|
requestHandler,
|
2014-05-09 05:11:29 -05:00
|
|
|
init;
|
2013-08-30 06:20:30 -05:00
|
|
|
|
2013-08-06 14:27:56 -05:00
|
|
|
// ## Request Handlers
|
2013-06-25 06:43:15 -05:00
|
|
|
|
2014-01-02 19:37:21 -05:00
|
|
|
function cacheInvalidationHeader(req, result) {
|
2013-09-24 10:21:43 -05:00
|
|
|
var parsedUrl = req._parsedUrl.pathname.replace(/\/$/, '').split('/'),
|
|
|
|
method = req.method,
|
2013-11-03 12:13:19 -05:00
|
|
|
endpoint = parsedUrl[4],
|
|
|
|
id = parsedUrl[5],
|
|
|
|
cacheInvalidate,
|
2014-04-21 20:04:30 -05:00
|
|
|
jsonResult = result.toJSON ? result.toJSON() : result,
|
|
|
|
post,
|
|
|
|
wasPublished,
|
|
|
|
wasDeleted;
|
2013-11-03 12:13:19 -05:00
|
|
|
|
2013-09-24 10:21:43 -05:00
|
|
|
if (method === 'POST' || method === 'PUT' || method === 'DELETE') {
|
2013-12-24 19:05:20 -05:00
|
|
|
if (endpoint === 'settings' || endpoint === 'users' || endpoint === 'db') {
|
2014-03-26 07:45:54 -05:00
|
|
|
cacheInvalidate = '/*';
|
2013-09-24 10:21:43 -05:00
|
|
|
} else if (endpoint === 'posts') {
|
2014-04-21 20:04:30 -05:00
|
|
|
post = jsonResult.posts[0];
|
|
|
|
wasPublished = post.statusChanged && post.status === 'published';
|
|
|
|
wasDeleted = method === 'DELETE';
|
|
|
|
|
|
|
|
// Remove the statusChanged value from the response
|
|
|
|
if (post.statusChanged) {
|
|
|
|
delete post.statusChanged;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Don't set x-cache-invalidate header for drafts
|
|
|
|
if (wasPublished || wasDeleted) {
|
|
|
|
cacheInvalidate = '/, /page/*, /rss/, /rss/*, /tag/*';
|
|
|
|
if (id && post.slug) {
|
|
|
|
return config.urlForPost(settings, post).then(function (postUrl) {
|
|
|
|
return cacheInvalidate + ', ' + postUrl;
|
|
|
|
});
|
|
|
|
}
|
2013-09-24 10:21:43 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-01-02 19:37:21 -05:00
|
|
|
|
|
|
|
return when(cacheInvalidate);
|
2013-09-24 10:21:43 -05:00
|
|
|
}
|
|
|
|
|
2014-05-01 18:42:23 -05:00
|
|
|
// if api request results in the creation of a new object, construct
|
|
|
|
// a Location: header that points to the new resource.
|
|
|
|
//
|
|
|
|
// arguments: request object, result object from the api call
|
|
|
|
// returns: a promise that will be fulfilled with the location of the
|
|
|
|
// resource
|
|
|
|
function locationHeader(req, result) {
|
|
|
|
var apiRoot = config.urlFor('api'),
|
|
|
|
location,
|
|
|
|
post,
|
|
|
|
notification,
|
|
|
|
parsedUrl = req._parsedUrl.pathname.replace(/\/$/, '').split('/'),
|
|
|
|
endpoint = parsedUrl[4];
|
|
|
|
|
|
|
|
if (req.method === 'POST') {
|
|
|
|
if (result.hasOwnProperty('posts')) {
|
|
|
|
post = result.posts[0];
|
|
|
|
location = apiRoot + '/posts/' + post.id + '/?status=' + post.status;
|
|
|
|
} else if (endpoint === 'notifications') {
|
2014-04-28 15:58:18 -05:00
|
|
|
notification = result.notifications;
|
|
|
|
location = apiRoot + '/notifications/' + notification[0].id;
|
2014-05-01 18:42:23 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return when(location);
|
|
|
|
}
|
|
|
|
|
2014-05-07 00:28:51 -05:00
|
|
|
// create a header that invokes the 'Save As' dialog
|
|
|
|
// in the browser when exporting the database to file.
|
|
|
|
// The 'filename' parameter is governed by [RFC6266](http://tools.ietf.org/html/rfc6266#section-4.3).
|
|
|
|
//
|
|
|
|
// for encoding whitespace and non-ISO-8859-1 characters, you MUST
|
|
|
|
// use the "filename*=" attribute, NOT "filename=". Ideally, both.
|
|
|
|
// see: http://tools.ietf.org/html/rfc598
|
|
|
|
// examples: http://tools.ietf.org/html/rfc6266#section-5
|
|
|
|
//
|
|
|
|
// we'll use ISO-8859-1 characters here to keep it simple.
|
|
|
|
function dbExportSaveAsHeader() {
|
|
|
|
// replace ':' with '_' for OS that don't support it
|
|
|
|
var now = (new Date()).toJSON().replace(/:/g, '_');
|
|
|
|
return 'Attachment; filename="ghost-' + now + '.json"';
|
|
|
|
}
|
|
|
|
|
2013-08-06 14:27:56 -05:00
|
|
|
// ### requestHandler
|
2013-06-25 06:43:15 -05: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) {
|
2014-01-06 09:05:31 -05:00
|
|
|
var options = _.extend(req.body, req.files, req.query, req.params),
|
2013-08-08 20:22:49 -05:00
|
|
|
apiContext = {
|
2014-04-03 08:03:09 -05:00
|
|
|
user: (req.session && req.session.user) ? req.session.user : null
|
2013-12-30 02:03:29 -05:00
|
|
|
};
|
2013-11-28 13:25:45 -05:00
|
|
|
|
2013-12-30 02:03:29 -05:00
|
|
|
return apiMethod.call(apiContext, options).then(function (result) {
|
2014-01-02 19:37:21 -05:00
|
|
|
return cacheInvalidationHeader(req, result).then(function (header) {
|
|
|
|
if (header) {
|
|
|
|
res.set({
|
|
|
|
"X-Cache-Invalidate": header
|
|
|
|
});
|
|
|
|
}
|
2014-05-01 18:42:23 -05:00
|
|
|
})
|
2014-05-07 00:28:51 -05:00
|
|
|
.then(function () {
|
|
|
|
if (apiMethod === db.exportContent) {
|
|
|
|
res.set({
|
|
|
|
"Content-Disposition": dbExportSaveAsHeader()
|
|
|
|
});
|
|
|
|
}
|
|
|
|
})
|
2014-05-01 18:42:23 -05:00
|
|
|
.then(function () {
|
|
|
|
return locationHeader(req, result).then(function (header) {
|
|
|
|
if (header) {
|
|
|
|
res.set({
|
|
|
|
'Location': header
|
|
|
|
});
|
|
|
|
}
|
2014-05-07 00:28:51 -05:00
|
|
|
|
2014-05-01 18:42:23 -05:00
|
|
|
res.json(result || {});
|
|
|
|
});
|
2014-01-02 19:37:21 -05:00
|
|
|
});
|
2013-12-30 02:03:29 -05:00
|
|
|
}, function (error) {
|
2014-05-05 08:51:21 -05:00
|
|
|
var errorCode,
|
|
|
|
errors = [];
|
|
|
|
|
|
|
|
if (!_.isArray(error)) {
|
|
|
|
error = [].concat(error);
|
|
|
|
}
|
|
|
|
|
2014-05-09 05:11:29 -05:00
|
|
|
_.each(error, function (errorItem) {
|
2014-05-05 08:51:21 -05:00
|
|
|
var errorContent = {};
|
2014-05-07 00:28:51 -05:00
|
|
|
|
2014-05-05 08:51:21 -05:00
|
|
|
//TODO: add logic to set the correct status code
|
2014-05-09 05:11:29 -05:00
|
|
|
errorCode = errorItem.code || 500;
|
|
|
|
|
|
|
|
errorContent['message'] = _.isString(errorItem) ? errorItem : (_.isObject(errorItem) ? errorItem.message : 'Unknown API Error');
|
|
|
|
errorContent['type'] = errorItem.type || 'InternalServerError';
|
2014-05-05 08:51:21 -05:00
|
|
|
errors.push(errorContent);
|
|
|
|
});
|
|
|
|
|
|
|
|
res.json(errorCode, {errors: errors});
|
2013-06-08 00:05:40 -05:00
|
|
|
});
|
|
|
|
};
|
2013-06-25 06:43:15 -05:00
|
|
|
};
|
|
|
|
|
2013-12-06 03:51:35 -05:00
|
|
|
init = function () {
|
|
|
|
return settings.updateSettingsCache();
|
|
|
|
};
|
|
|
|
|
2013-08-06 14:27:56 -05:00
|
|
|
// Public API
|
2013-12-06 03:51:35 -05:00
|
|
|
module.exports = {
|
|
|
|
posts: posts,
|
|
|
|
users: users,
|
|
|
|
tags: tags,
|
|
|
|
notifications: notifications,
|
|
|
|
settings: settings,
|
|
|
|
db: db,
|
2014-04-03 20:59:09 -05:00
|
|
|
mail: mail,
|
2013-12-06 03:51:35 -05:00
|
|
|
requestHandler: requestHandler,
|
|
|
|
init: init
|
|
|
|
};
|