2
1
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2023-12-13 21:00:40 +01:00

Merge pull request #2706 from appleYaks/db-api-update

DB API returns JSON-API compatible objects. Export triggers 'Save As' di...
This commit is contained in:
Hannah Wolfe 2014-05-08 11:28:02 +01:00
commit 1c3ba536c9
4 changed files with 133 additions and 9 deletions

View file

@ -20,7 +20,9 @@ db = {
// Export data, otherwise send error 500
return canThis(self.user).exportContent.db().then(function () {
return dataExport().otherwise(function (error) {
return dataExport().then(function (exportedData) {
return when.resolve({ db: [exportedData] });
}).otherwise(function (error) {
return when.reject({type: 'InternalServerError', message: error.message || error});
});
}, function () {
@ -46,7 +48,7 @@ db = {
return api.settings.read.call({ internal: true }, { key: 'databaseVersion' }).then(function (response) {
var setting = response.settings[0];
return when(setting.value);
}, function () {
return when('002');
@ -90,7 +92,7 @@ db = {
}).then(function importSuccess() {
return api.settings.updateSettingsCache();
}).then(function () {
return when.resolve({message: 'Posts, tags and other data successfully imported'});
return when.resolve({ db: [] });
}).otherwise(function importFailure(error) {
return when.reject({type: 'InternalServerError', message: error.message || error});
}).finally(function () {
@ -107,7 +109,7 @@ db = {
return canThis(self.user).deleteAllContent.db().then(function () {
return when(dataProvider.deleteAllContent())
.then(function () {
return when.resolve({message: 'Successfully deleted all content from your blog.'});
return when.resolve({ db: [] });
}, function (error) {
return when.reject({message: error.message || error});
});

View file

@ -109,6 +109,22 @@ function locationHeader(req, result) {
return when(location);
}
// 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"';
}
// ### requestHandler
// 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
@ -127,6 +143,13 @@ requestHandler = function (apiMethod) {
});
}
})
.then(function () {
if (apiMethod === db.exportContent) {
res.set({
"Content-Disposition": dbExportSaveAsHeader()
});
}
})
.then(function () {
return locationHeader(req, result).then(function (header) {
if (header) {
@ -134,7 +157,7 @@ requestHandler = function (apiMethod) {
'Location': header
});
}
res.json(result || {});
});
});
@ -148,10 +171,10 @@ requestHandler = function (apiMethod) {
_.each(error, function (erroritem) {
var errorContent = {};
//TODO: add logic to set the correct status code
errorCode = errorTypes[erroritem.type].code || 500;
errorContent['message'] = _.isString(erroritem) ? erroritem : (_.isObject(erroritem) ? erroritem.message : 'Unknown API Error');
errorContent['type'] = erroritem.type || 'InternalServerError';
errors.push(errorContent);

View file

@ -0,0 +1,98 @@
/*global describe, it, before, after */
var supertest = require('supertest'),
express = require('express'),
should = require('should'),
testUtils = require('../../../utils'),
ghost = require('../../../../../core'),
httpServer,
request;
describe('DB API', function () {
var user = testUtils.DataGenerator.forModel.users[0],
csrfToken = '';
before(function (done) {
var app = express();
ghost({app: app}).then(function (_httpServer) {
httpServer = _httpServer;
// request = supertest(app);
request = supertest.agent(app);
testUtils.clearData()
.then(function () {
return testUtils.initData();
})
.then(function () {
return testUtils.insertDefaultFixtures();
})
.then(function () {
request.get('/ghost/signin/')
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
var pattern_meta = /<meta.*?name="csrf-param".*?content="(.*?)".*?>/i;
pattern_meta.should.exist;
csrfToken = res.text.match(pattern_meta)[1];
process.nextTick(function() {
request.post('/ghost/signin/')
.set('X-CSRF-Token', csrfToken)
.send({email: user.email, password: user.password})
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
request.saveCookies(res);
request.get('/ghost/')
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
csrfToken = res.text.match(pattern_meta)[1];
done();
});
});
});
});
}).catch(done);
}).catch(function (e) {
console.log('Ghost Error: ', e);
console.log(e.stack);
});
});
after(function () {
httpServer.close();
});
it('attaches the Content-Disposition header on export', function (done) {
request.get(testUtils.API.getApiQuery('db/'))
.expect(200)
.expect('Content-Disposition', /Attachment; filename="[A-Za-z0-9._-]+\.json"/)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
res.should.be.json;
var jsonResponse = res.body;
should.exist(jsonResponse.db);
jsonResponse.db.should.have.length(1);
done();
});
});
});

View file

@ -39,8 +39,9 @@ describe('DB API', function () {
permissions.init().then(function () {
return dbAPI.deleteAllContent.call({user: 1});
}).then(function (result) {
should.exist(result.message);
result.message.should.equal('Successfully deleted all content from your blog.');
should.exist(result.db);
result.db.should.be.instanceof(Array);
result.db.should.be.empty;
}).then(function () {
TagsAPI.browse().then(function (results) {
should.exist(results);