0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-10 23:36:14 -05:00

Added v2 actions endpoint

refs #10431

- added v2 endpoint with browse permissions
- context.integration was never accessible in the model layer
  - why? https://github.com/TryGhost/Ghost/issues/10099
This commit is contained in:
kirrg001 2019-01-29 19:52:05 +01:00 committed by Katharina Irrgang
parent 8bb2c7d3d5
commit 2fd4cbb93b
10 changed files with 245 additions and 0 deletions

View file

@ -0,0 +1,38 @@
const models = require('../../models');
module.exports = {
docName: 'actions',
browse: {
options: [
'page',
'limit',
'fields'
],
data: [
'id',
'type'
],
validation: {
id: {
required: true
},
type: {
required: true,
values: ['resource', 'actor']
}
},
permissions: true,
query(frame) {
if (frame.data.type === 'resource') {
frame.options.withRelated = ['actor'];
frame.options.filter = `resource_id:${frame.data.id}`;
} else {
frame.options.withRelated = ['resource'];
frame.options.filter = `actor_id:${frame.data.id}`;
}
return models.Action.findPage(frame.options);
}
}
};

View file

@ -109,5 +109,9 @@ module.exports = {
get themes() {
return shared.pipeline(require('./themes'), localUtils);
},
get actions() {
return shared.pipeline(require('./actions'), localUtils);
}
};

View file

@ -0,0 +1,15 @@
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:actions');
const mapper = require('./utils/mapper');
module.exports = {
browse(models, apiConfig, frame) {
debug('browse');
frame.response = {
actions: models.data.map(model => mapper.mapAction(model, frame)),
meta: models.meta
};
debug(frame.response);
}
};

View file

@ -89,5 +89,9 @@ module.exports = {
get themes() {
return require('./themes');
},
get actions() {
return require('./actions');
}
};

View file

@ -1,3 +1,4 @@
const _ = require('lodash');
const localUtils = require('../../../index');
const tag = (attrs) => {
@ -98,6 +99,32 @@ const post = (attrs, frame) => {
return attrs;
};
const action = (attrs) => {
if (attrs.actor) {
delete attrs.actor_id;
delete attrs.resource_id;
if (attrs.actor_type === 'user') {
attrs.actor = _.pick(attrs.actor, ['id', 'name', 'slug', 'profile_image']);
attrs.actor.image = attrs.actor.profile_image;
delete attrs.actor.profile_image;
} else {
attrs.actor = _.pick(attrs.actor, ['id', 'name', 'slug', 'icon_image']);
attrs.actor.image = attrs.actor.icon_image;
delete attrs.actor.icon_image;
}
} else if (attrs.resource) {
delete attrs.actor_id;
delete attrs.resource_id;
// @NOTE: we only support posts right now
attrs.resource = _.pick(attrs.resource, ['id', 'title', 'slug', 'feature_image']);
attrs.resource.image = attrs.resource.feature_image;
delete attrs.resource.feature_image;
}
};
module.exports.post = post;
module.exports.tag = tag;
module.exports.author = author;
module.exports.action = action;

View file

@ -95,9 +95,16 @@ const mapImage = (path) => {
return url.forImage(path);
};
const mapAction = (model, frame) => {
const attrs = model.toJSON(frame.options);
clean.action(attrs);
return attrs;
};
module.exports.mapPost = mapPost;
module.exports.mapUser = mapUser;
module.exports.mapTag = mapTag;
module.exports.mapIntegration = mapIntegration;
module.exports.mapSettings = mapSettings;
module.exports.mapImage = mapImage;
module.exports.mapAction = mapAction;

View file

@ -13,6 +13,7 @@ module.exports = function parseContext(context) {
user: null,
api_key: null,
app: null,
integration: null,
public: true
};
@ -34,6 +35,7 @@ module.exports = function parseContext(context) {
if (context && context.api_key) {
parsed.api_key = context.api_key;
parsed.integration = context.integration;
parsed.public = (context.api_key.type === 'content');
}

View file

@ -256,5 +256,8 @@ module.exports = function apiRoutes() {
// ## Oembed (fetch response from oembed provider)
router.get('/oembed', mw.authAdminApi, apiv2.http(apiv2.oembed.read));
// ## Actions
router.get('/actions/:type/:id', mw.authAdminApi, apiv2.http(apiv2.actions.browse));
return router;
};

View file

@ -0,0 +1,142 @@
const should = require('should');
const Promise = require('bluebird');
const supertest = require('supertest');
const testUtils = require('../../../utils');
const localUtils = require('./utils');
const config = require('../../../../server/config');
const ghost = testUtils.startGhost;
let request;
describe('Actions API', function () {
before(function () {
return ghost()
.then(function () {
request = supertest.agent(config.get('url'));
})
.then(function () {
return localUtils.doAuth(request, 'integrations', 'api_keys');
});
});
// @NOTE: This test runs a little slower, because we store Dates without milliseconds.
it('Can request actions for resource', function () {
let postId;
return request
.post(localUtils.API.getApiQuery('posts/'))
.set('Origin', config.get('url'))
.send({
posts: [{
title: 'test post'
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(201)
.then((res) => {
postId = res.body.posts[0].id;
return request
.get(localUtils.API.getApiQuery(`actions/resource/${postId}/`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
})
.then((res) => {
localUtils.API.checkResponse(res.body, 'actions');
localUtils.API.checkResponse(res.body.actions[0], 'action');
res.body.actions.length.should.eql(1);
res.body.actions[0].resource_type.should.eql('post');
res.body.actions[0].actor_type.should.eql('user');
res.body.actions[0].event.should.eql('added');
Object.keys(res.body.actions[0].actor).length.should.eql(4);
res.body.actions[0].actor.id.should.eql(testUtils.DataGenerator.Content.users[0].id);
res.body.actions[0].actor.image.should.eql(testUtils.DataGenerator.Content.users[0].profile_image);
res.body.actions[0].actor.name.should.eql(testUtils.DataGenerator.Content.users[0].name);
res.body.actions[0].actor.slug.should.eql(testUtils.DataGenerator.Content.users[0].slug);
return Promise.delay(1000);
})
.then(() => {
return request
.put(localUtils.API.getApiQuery(`posts/${postId}/`))
.set('Origin', config.get('url'))
.send({
posts: [{
slug: 'new-slug'
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
})
.then(() => {
return request
.get(localUtils.API.getApiQuery(`actions/resource/${postId}/`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
})
.then((res) => {
localUtils.API.checkResponse(res.body, 'actions');
localUtils.API.checkResponse(res.body.actions[0], 'action');
res.body.actions.length.should.eql(2);
res.body.actions[0].resource_type.should.eql('post');
res.body.actions[0].actor_type.should.eql('user');
res.body.actions[0].event.should.eql('edited');
Object.keys(res.body.actions[0].actor).length.should.eql(4);
res.body.actions[0].actor.id.should.eql(testUtils.DataGenerator.Content.users[0].id);
res.body.actions[0].actor.image.should.eql(testUtils.DataGenerator.Content.users[0].profile_image);
res.body.actions[0].actor.name.should.eql(testUtils.DataGenerator.Content.users[0].name);
res.body.actions[0].actor.slug.should.eql(testUtils.DataGenerator.Content.users[0].slug);
return Promise.delay(1000);
})
.then(() => {
const integrationRequest = supertest.agent(config.get('url'));
return integrationRequest
.put(localUtils.API.getApiQuery(`posts/${postId}/`))
.set('Origin', config.get('url'))
.set('Authorization', `Ghost ${localUtils.getValidAdminToken(localUtils.API.getApiQuery(`posts/${postId}/`))}`)
.send({
posts: [{
featured: true
}]
})
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
})
.then(() => {
return request
.get(localUtils.API.getApiQuery(`actions/resource/${postId}/`))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
})
.then((res) => {
localUtils.API.checkResponse(res.body, 'actions');
localUtils.API.checkResponse(res.body.actions[0], 'action');
res.body.actions.length.should.eql(3);
res.body.actions[0].resource_type.should.eql('post');
res.body.actions[0].actor_type.should.eql('integration');
res.body.actions[0].event.should.eql('edited');
Object.keys(res.body.actions[0].actor).length.should.eql(4);
res.body.actions[0].actor.id.should.eql(testUtils.DataGenerator.Content.integrations[0].id);
should.equal(res.body.actions[0].actor.image, null);
res.body.actions[0].actor.name.should.eql(testUtils.DataGenerator.Content.integrations[0].name);
res.body.actions[0].actor.slug.should.eql(testUtils.DataGenerator.Content.integrations[0].slug);
});
});
});

View file

@ -17,6 +17,9 @@ const expectedProperties = {
slug: ['slug'],
invites: ['invites', 'meta'],
themes: ['themes'],
actions: ['actions', 'meta'],
action: ['id', 'resource_type', 'actor_type', 'event', 'created_at', 'actor'],
post: _(schema.posts)
.keys()