mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-27 22:49:56 -05:00
parent
310526b6c5
commit
1ee4d53bfe
6 changed files with 358 additions and 6 deletions
|
@ -53,5 +53,9 @@ module.exports = {
|
|||
|
||||
get upload() {
|
||||
return shared.pipeline(require('./upload'), localUtils);
|
||||
},
|
||||
|
||||
get tags() {
|
||||
return shared.pipeline(require('./tags'), localUtils);
|
||||
}
|
||||
};
|
||||
|
|
147
core/server/api/v2/tags.js
Normal file
147
core/server/api/v2/tags.js
Normal file
|
@ -0,0 +1,147 @@
|
|||
const Promise = require('bluebird');
|
||||
const common = require('../../lib/common');
|
||||
const models = require('../../models');
|
||||
const ALLOWED_INCLUDES = ['count.posts'];
|
||||
|
||||
module.exports = {
|
||||
docName: 'tags',
|
||||
|
||||
browse: {
|
||||
options: [
|
||||
'include',
|
||||
'filter',
|
||||
'fields',
|
||||
'limit',
|
||||
'order',
|
||||
'debug'
|
||||
],
|
||||
validation: {
|
||||
options: {
|
||||
include: {
|
||||
values: ALLOWED_INCLUDES
|
||||
}
|
||||
}
|
||||
},
|
||||
permissions: true,
|
||||
query(frame) {
|
||||
return models.Tag.findPage(frame.options);
|
||||
}
|
||||
},
|
||||
|
||||
read: {
|
||||
options: [
|
||||
'include',
|
||||
'filter',
|
||||
'fields',
|
||||
'debug'
|
||||
],
|
||||
data: [
|
||||
'id',
|
||||
'slug',
|
||||
'visibility'
|
||||
],
|
||||
validation: {
|
||||
options: {
|
||||
include: {
|
||||
values: ALLOWED_INCLUDES
|
||||
}
|
||||
}
|
||||
},
|
||||
permissions: true,
|
||||
query(frame) {
|
||||
return models.Tag.findOne(frame.data, frame.options)
|
||||
.then((model) => {
|
||||
if (!model) {
|
||||
return Promise.reject(new common.errors.NotFoundError({
|
||||
message: common.i18n.t('errors.api.tags.tagNotFound')
|
||||
}));
|
||||
}
|
||||
|
||||
return model;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
add: {
|
||||
statusCode: 201,
|
||||
headers: {
|
||||
cacheInvalidate: true
|
||||
},
|
||||
options: [
|
||||
'include'
|
||||
],
|
||||
validation: {
|
||||
options: {
|
||||
include: {
|
||||
values: ALLOWED_INCLUDES
|
||||
}
|
||||
},
|
||||
data: {
|
||||
name: {
|
||||
required: true
|
||||
}
|
||||
}
|
||||
},
|
||||
permissions: true,
|
||||
query(frame) {
|
||||
return models.Tag.add(frame.data.tags[0], frame.options);
|
||||
}
|
||||
},
|
||||
|
||||
edit: {
|
||||
headers: {
|
||||
cacheInvalidate: true
|
||||
},
|
||||
options: [
|
||||
'id',
|
||||
'include'
|
||||
],
|
||||
validation: {
|
||||
options: {
|
||||
include: {
|
||||
values: ALLOWED_INCLUDES
|
||||
},
|
||||
id: {
|
||||
required: true
|
||||
}
|
||||
}
|
||||
},
|
||||
permissions: true,
|
||||
query(frame) {
|
||||
return models.Tag.edit(frame.data.tags[0], frame.options)
|
||||
.then((model) => {
|
||||
if (!model) {
|
||||
return Promise.reject(new common.errors.NotFoundError({
|
||||
message: common.i18n.t('errors.api.tags.tagNotFound')
|
||||
}));
|
||||
}
|
||||
|
||||
return model;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
destroy: {
|
||||
statusCode: 204,
|
||||
headers: {
|
||||
cacheInvalidate: true
|
||||
},
|
||||
options: [
|
||||
'id'
|
||||
],
|
||||
validation: {
|
||||
options: {
|
||||
include: {
|
||||
values: ALLOWED_INCLUDES
|
||||
},
|
||||
id: {
|
||||
required: true
|
||||
}
|
||||
}
|
||||
},
|
||||
permissions: true,
|
||||
query(frame) {
|
||||
return models.Tag.destroy(frame.options).return(null);
|
||||
}
|
||||
}
|
||||
};
|
|
@ -41,5 +41,9 @@ module.exports = {
|
|||
|
||||
get upload() {
|
||||
return require('./upload');
|
||||
},
|
||||
|
||||
get tags() {
|
||||
return require('./tags');
|
||||
}
|
||||
};
|
||||
|
|
37
core/server/api/v2/utils/serializers/output/tags.js
Normal file
37
core/server/api/v2/utils/serializers/output/tags.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
const debug = require('ghost-ignition').debug('api:v2:utils:serializers:output:tags');
|
||||
const urlService = require('../../../../../services/url');
|
||||
|
||||
const absoluteUrls = (tag) => {
|
||||
tag.url = urlService.getUrlByResourceId(tag.id, {absolute: true});
|
||||
|
||||
if (tag.feature_image) {
|
||||
tag.feature_image = urlService.utils.urlFor('image', {image: tag.feature_image}, true);
|
||||
}
|
||||
|
||||
return tag;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
all(models, apiConfig, frame) {
|
||||
debug('all');
|
||||
|
||||
if (!models) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (models.meta) {
|
||||
frame.response = {
|
||||
tags: models.data.map(model => absoluteUrls(model.toJSON(frame.options))),
|
||||
meta: models.meta
|
||||
};
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
frame.response = {
|
||||
tags: [absoluteUrls(models.toJSON(frame.options))]
|
||||
};
|
||||
|
||||
debug(frame.response);
|
||||
}
|
||||
};
|
|
@ -65,12 +65,12 @@ module.exports = function apiRoutes() {
|
|||
router.del('/users/:id', mw.authAdminAPI, api.http(api.users.destroy));
|
||||
|
||||
// ## Tags
|
||||
router.get('/tags', mw.authAdminAPI, api.http(api.tags.browse));
|
||||
router.get('/tags/:id', mw.authAdminAPI, api.http(api.tags.read));
|
||||
router.get('/tags/slug/:slug', mw.authAdminAPI, api.http(api.tags.read));
|
||||
router.post('/tags', mw.authAdminAPI, api.http(api.tags.add));
|
||||
router.put('/tags/:id', mw.authAdminAPI, api.http(api.tags.edit));
|
||||
router.del('/tags/:id', mw.authAdminAPI, api.http(api.tags.destroy));
|
||||
router.get('/tags', mw.authAdminAPI, apiv2.http(apiv2.tags.browse));
|
||||
router.get('/tags/:id', mw.authAdminAPI, apiv2.http(apiv2.tags.read));
|
||||
router.get('/tags/slug/:slug', mw.authAdminAPI, apiv2.http(apiv2.tags.read));
|
||||
router.post('/tags', mw.authAdminAPI, apiv2.http(apiv2.tags.add));
|
||||
router.put('/tags/:id', mw.authAdminAPI, apiv2.http(apiv2.tags.edit));
|
||||
router.del('/tags/:id', mw.authAdminAPI, apiv2.http(apiv2.tags.destroy));
|
||||
|
||||
// ## Subscribers
|
||||
router.get('/subscribers', shared.middlewares.labs.subscribers, mw.authAdminAPI, apiv2.http(apiv2.subscribers.browse));
|
||||
|
|
160
core/test/functional/api/v2/admin/tags_spec.js
Normal file
160
core/test/functional/api/v2/admin/tags_spec.js
Normal file
|
@ -0,0 +1,160 @@
|
|||
const should = require('should');
|
||||
const supertest = require('supertest');
|
||||
const testUtils = require('../../../../utils');
|
||||
const localUtils = require('./utils');
|
||||
const config = require('../../../../../../core/server/config');
|
||||
const ghost = testUtils.startGhost;
|
||||
let request;
|
||||
|
||||
describe('Tag API V2', function () {
|
||||
let ghostServer;
|
||||
|
||||
before(function () {
|
||||
return ghost()
|
||||
.then(function (_ghostServer) {
|
||||
ghostServer = _ghostServer;
|
||||
request = supertest.agent(config.get('url'));
|
||||
})
|
||||
.then(function () {
|
||||
return localUtils.doAuth(request, 'posts');
|
||||
});
|
||||
});
|
||||
|
||||
it('browse', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery('tags/?include=count.posts&order=name%20DESC'))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.tags);
|
||||
jsonResponse.tags.should.have.length(6);
|
||||
testUtils.API.checkResponse(jsonResponse.tags[0], 'tag', ['count', 'url']);
|
||||
|
||||
testUtils.API.isISO8601(jsonResponse.tags[0].created_at).should.be.true();
|
||||
jsonResponse.tags[0].created_at.should.be.an.instanceof(String);
|
||||
|
||||
jsonResponse.meta.pagination.should.have.property('page', 1);
|
||||
jsonResponse.meta.pagination.should.have.property('limit', 15);
|
||||
jsonResponse.meta.pagination.should.have.property('pages', 1);
|
||||
jsonResponse.meta.pagination.should.have.property('total', 6);
|
||||
jsonResponse.meta.pagination.should.have.property('next', null);
|
||||
jsonResponse.meta.pagination.should.have.property('prev', null);
|
||||
|
||||
jsonResponse.tags[0].url.should.eql(`${config.get('url')}/tag/pollo/`);
|
||||
|
||||
should.exist(jsonResponse.tags[0].count.posts);
|
||||
});
|
||||
});
|
||||
|
||||
it('read', function () {
|
||||
return request
|
||||
.get(localUtils.API.getApiQuery(`tags/${testUtils.existingData.tags[0].id}/?include=count.posts`))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
should.not.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.tags);
|
||||
jsonResponse.tags.should.have.length(1);
|
||||
testUtils.API.checkResponse(jsonResponse.tags[0], 'tag', ['count', 'url']);
|
||||
should.exist(jsonResponse.tags[0].count.posts);
|
||||
|
||||
jsonResponse.tags[0].url.should.eql(`${config.get('url')}/tag/getting-started/`);
|
||||
});
|
||||
});
|
||||
|
||||
it('add', function () {
|
||||
const tag = testUtils.DataGenerator.forKnex.createTag();
|
||||
|
||||
return request
|
||||
.post(localUtils.API.getApiQuery('tags/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({
|
||||
tags: [tag]
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(201)
|
||||
.then((res) => {
|
||||
should.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.tags);
|
||||
jsonResponse.tags.should.have.length(1);
|
||||
// @TODO: model layer has no defaults for these properties
|
||||
testUtils.API.checkResponse(jsonResponse.tags[0], 'tag', ['url'], [
|
||||
'feature_image',
|
||||
'meta_description',
|
||||
'meta_title',
|
||||
'parent'
|
||||
]);
|
||||
testUtils.API.isISO8601(jsonResponse.tags[0].created_at).should.be.true();
|
||||
});
|
||||
});
|
||||
|
||||
it('add internal', function () {
|
||||
const tag = testUtils.DataGenerator.forKnex.createTag({
|
||||
name: '#test',
|
||||
slug: null
|
||||
});
|
||||
|
||||
return request
|
||||
.post(localUtils.API.getApiQuery('tags/'))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({
|
||||
tags: [tag]
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(201)
|
||||
.then((res) => {
|
||||
should.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
jsonResponse.tags[0].visibility.should.eql('internal');
|
||||
jsonResponse.tags[0].name.should.eql('#test');
|
||||
jsonResponse.tags[0].slug.should.eql('hash-test');
|
||||
});
|
||||
});
|
||||
|
||||
it('edit', function () {
|
||||
return request
|
||||
.put(localUtils.API.getApiQuery(`tags/${testUtils.existingData.tags[0].id}`))
|
||||
.set('Origin', config.get('url'))
|
||||
.send({
|
||||
tags: [Object.assign({}, testUtils.existingData.tags[0], {description: 'hey ho ab ins klo'})]
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(200)
|
||||
.then((res) => {
|
||||
should.exist(res.headers['x-cache-invalidate']);
|
||||
const jsonResponse = res.body;
|
||||
should.exist(jsonResponse);
|
||||
should.exist(jsonResponse.tags);
|
||||
jsonResponse.tags.should.have.length(1);
|
||||
testUtils.API.checkResponse(jsonResponse.tags[0], 'tag', ['url']);
|
||||
jsonResponse.tags[0].description.should.eql('hey ho ab ins klo');
|
||||
});
|
||||
});
|
||||
|
||||
it('destroy', function () {
|
||||
return request
|
||||
.del(localUtils.API.getApiQuery(`tags/${testUtils.existingData.tags[0].id}`))
|
||||
.set('Origin', config.get('url'))
|
||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||
.expect(204)
|
||||
.then((res) => {
|
||||
should.exist(res.headers['x-cache-invalidate']);
|
||||
res.body.should.eql({});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Reference in a new issue