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:
parent
8bb2c7d3d5
commit
2fd4cbb93b
10 changed files with 245 additions and 0 deletions
38
core/server/api/v2/actions.js
Normal file
38
core/server/api/v2/actions.js
Normal 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);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -109,5 +109,9 @@ module.exports = {
|
|||
|
||||
get themes() {
|
||||
return shared.pipeline(require('./themes'), localUtils);
|
||||
},
|
||||
|
||||
get actions() {
|
||||
return shared.pipeline(require('./actions'), localUtils);
|
||||
}
|
||||
};
|
||||
|
|
15
core/server/api/v2/utils/serializers/output/actions.js
Normal file
15
core/server/api/v2/utils/serializers/output/actions.js
Normal 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);
|
||||
}
|
||||
};
|
|
@ -89,5 +89,9 @@ module.exports = {
|
|||
|
||||
get themes() {
|
||||
return require('./themes');
|
||||
},
|
||||
|
||||
get actions() {
|
||||
return require('./actions');
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
142
core/test/acceptance/old/admin/actions_spec.js
Normal file
142
core/test/acceptance/old/admin/actions_spec.js
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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()
|
||||
|
|
Loading…
Add table
Reference in a new issue