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() {
|
get themes() {
|
||||||
return shared.pipeline(require('./themes'), localUtils);
|
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() {
|
get themes() {
|
||||||
return require('./themes');
|
return require('./themes');
|
||||||
|
},
|
||||||
|
|
||||||
|
get actions() {
|
||||||
|
return require('./actions');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
const _ = require('lodash');
|
||||||
const localUtils = require('../../../index');
|
const localUtils = require('../../../index');
|
||||||
|
|
||||||
const tag = (attrs) => {
|
const tag = (attrs) => {
|
||||||
|
@ -98,6 +99,32 @@ const post = (attrs, frame) => {
|
||||||
return attrs;
|
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.post = post;
|
||||||
module.exports.tag = tag;
|
module.exports.tag = tag;
|
||||||
module.exports.author = author;
|
module.exports.author = author;
|
||||||
|
module.exports.action = action;
|
||||||
|
|
|
@ -95,9 +95,16 @@ const mapImage = (path) => {
|
||||||
return url.forImage(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.mapPost = mapPost;
|
||||||
module.exports.mapUser = mapUser;
|
module.exports.mapUser = mapUser;
|
||||||
module.exports.mapTag = mapTag;
|
module.exports.mapTag = mapTag;
|
||||||
module.exports.mapIntegration = mapIntegration;
|
module.exports.mapIntegration = mapIntegration;
|
||||||
module.exports.mapSettings = mapSettings;
|
module.exports.mapSettings = mapSettings;
|
||||||
module.exports.mapImage = mapImage;
|
module.exports.mapImage = mapImage;
|
||||||
|
module.exports.mapAction = mapAction;
|
||||||
|
|
|
@ -13,6 +13,7 @@ module.exports = function parseContext(context) {
|
||||||
user: null,
|
user: null,
|
||||||
api_key: null,
|
api_key: null,
|
||||||
app: null,
|
app: null,
|
||||||
|
integration: null,
|
||||||
public: true
|
public: true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -34,6 +35,7 @@ module.exports = function parseContext(context) {
|
||||||
|
|
||||||
if (context && context.api_key) {
|
if (context && context.api_key) {
|
||||||
parsed.api_key = context.api_key;
|
parsed.api_key = context.api_key;
|
||||||
|
parsed.integration = context.integration;
|
||||||
parsed.public = (context.api_key.type === 'content');
|
parsed.public = (context.api_key.type === 'content');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -256,5 +256,8 @@ module.exports = function apiRoutes() {
|
||||||
// ## Oembed (fetch response from oembed provider)
|
// ## Oembed (fetch response from oembed provider)
|
||||||
router.get('/oembed', mw.authAdminApi, apiv2.http(apiv2.oembed.read));
|
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;
|
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'],
|
slug: ['slug'],
|
||||||
invites: ['invites', 'meta'],
|
invites: ['invites', 'meta'],
|
||||||
themes: ['themes'],
|
themes: ['themes'],
|
||||||
|
actions: ['actions', 'meta'],
|
||||||
|
|
||||||
|
action: ['id', 'resource_type', 'actor_type', 'event', 'created_at', 'actor'],
|
||||||
|
|
||||||
post: _(schema.posts)
|
post: _(schema.posts)
|
||||||
.keys()
|
.keys()
|
||||||
|
|
Loading…
Add table
Reference in a new issue