diff --git a/core/server/api/index.js b/core/server/api/index.js index 90145559d2..8299eafcb4 100644 --- a/core/server/api/index.js +++ b/core/server/api/index.js @@ -13,13 +13,14 @@ var _ = require('lodash'), tags = require('./tags'), themes = require('./themes'), users = require('./users'), + slugs = require('./slugs'), http, formatHttpErrors, cacheInvalidationHeader, locationHeader, contentDispositionHeader, - init, + init; /** * ### Init @@ -247,5 +248,6 @@ module.exports = { settings: settings, tags: tags, themes: themes, - users: users + users: users, + slugs: slugs }; diff --git a/core/server/api/posts.js b/core/server/api/posts.js index 0a79ce3bfb..0029f886b1 100644 --- a/core/server/api/posts.js +++ b/core/server/api/posts.js @@ -168,26 +168,6 @@ posts = { }, function () { return when.reject(new errors.NoPermissionError('You do not have permission to remove posts.')); }); - }, - - /** - * ## Generate Slug - * Create a unique slug for a given post title - * @param {{title (required), transacting}} options - * @returns {Promise(String)} Unique string - */ - generateSlug: function generateSlug(options) { - return canThis(options.context).slug.post().then(function () { - return dataProvider.Base.Model.generateSlug(dataProvider.Post, options.title, {status: 'all'}) - .then(function (slug) { - if (slug) { - return slug; - } - return when.reject(new errors.InternalServerError('Could not generate slug')); - }); - }, function () { - return when.reject(new errors.NoPermissionError('You do not have permission.')); - }); } }; diff --git a/core/server/api/slugs.js b/core/server/api/slugs.js new file mode 100644 index 0000000000..7c5b40e793 --- /dev/null +++ b/core/server/api/slugs.js @@ -0,0 +1,46 @@ +var canThis = require('../permissions').canThis, + dataProvider = require('../models'), + errors = require('../errors'), + when = require('when'), + + slugs, + // `allowedTypes` is used to define allowed slug types and map them against it's model class counterpart + allowedTypes = { + post: dataProvider.Post, + tag: dataProvider.Tag + }; + +/** + * ## Generate Slug + * Create a unique slug for a given post title + * @param {{type (required), context}} options + * @param {{title (required), transacting}} options + * @returns {Promise(String)} Unique string + */ +slugs = { + + // #### Generate slug + // **takes:** a string to generate the slug from + generate: function (options) { + options = options || {}; + + return canThis(options.context).generate.slug().then(function () { + if (allowedTypes[options.type] === undefined) { + return when.reject(new errors.BadRequestError('Unknown slug type \'' + options.type + '\'.')); + } + + return dataProvider.Base.Model.generateSlug(allowedTypes[options.type], options.title, {status: 'all'}).then(function (slug) { + if (!slug) { + return when.reject(new errors.InternalServerError('Could not generate slug.')); + } + + return { slugs: [{ slug: slug }] }; + }); + }, function () { + return when.reject(new errors.NoPermissionError('You do not have permission to generate a slug.')); + }); + } + +}; + +module.exports = slugs; \ No newline at end of file diff --git a/core/server/data/fixtures/index.js b/core/server/data/fixtures/index.js index a7d5d698ed..359e966f0e 100644 --- a/core/server/data/fixtures/index.js +++ b/core/server/data/fixtures/index.js @@ -71,9 +71,14 @@ var fixtures = { permissions003: [ { - "name": "Get slug", - "action_type": "slug", - "object_type": "post" + "name": "Generate post slug", + "action_type": "generate", + "object_type": "slug" + }, + { + "name": "Generate tag slug", + "action_type": "generate", + "object_type": "slug" }, { "name": "Export database", @@ -185,7 +190,7 @@ populateFixtures = function () { Role.forge({name: 'Editor'}).fetch({withRelated: ['permissions']}).then(function (role) { Permissions.forge().fetch().then(function (perms) { var editor_perm = _.map(perms.toJSON(), function (perm) { - if (perm.object_type === 'post' || perm.object_type === 'user') { + if (perm.object_type === 'post' || perm.object_type === 'user' || perm.object_type === 'slug') { return perm.id; } if (perm.object_type === 'setting' && @@ -198,12 +203,14 @@ populateFixtures = function () { }); }); - // author gets access to post.add, post.slug, settings.browse, settings.read, users.browse and users.read + // author gets access to post.add, slug.generate, settings.browse, settings.read, users.browse and users.read Role.forge({name: 'Author'}).fetch({withRelated: ['permissions']}).then(function (role) { Permissions.forge().fetch().then(function (perms) { var author_perm = _.map(perms.toJSON(), function (perm) { - if (perm.object_type === 'post' && - (perm.action_type === 'add' || perm.action_type === 'slug')) { + if (perm.object_type === 'post' && perm.action_type === 'add') { + return perm.id; + } + if (perm.object_type === 'slug' && perm.action_type === 'generate') { return perm.id; } if (perm.object_type === 'setting' && @@ -272,8 +279,10 @@ updateFixtures = function () { Role.forge({name: 'Author'}).fetch({withRelated: ['permissions']}).then(function (role) { Permissions.forge().fetch().then(function (perms) { var author_perm = _.map(perms.toJSON(), function (perm) { - if (perm.object_type === 'post' && - (perm.action_type === 'add' || perm.action_type === 'slug')) { + if (perm.object_type === 'post' && perm.action_type === 'add') { + return perm.id; + } + if (perm.object_type === 'slug' && perm.action_type === 'generate') { return perm.id; } if (perm.object_type === 'setting' && diff --git a/core/server/routes/api.js b/core/server/routes/api.js index 76e7686c52..b1800f0410 100644 --- a/core/server/routes/api.js +++ b/core/server/routes/api.js @@ -38,6 +38,8 @@ apiRoutes = function (server) { // ## Mail server.post('/ghost/api/v0.1/mail', api.http(api.mail.send)); server.post('/ghost/api/v0.1/mail/test', api.http(api.mail.sendTest)); + // #### Slugs + server.get('/ghost/api/v0.1/slugs/:type/:title', api.http(api.slugs.generate)); }; -module.exports = apiRoutes; \ No newline at end of file +module.exports = apiRoutes; diff --git a/core/test/integration/api/api_db_spec.js b/core/test/integration/api/api_db_spec.js index 9ac5d2c628..d3420000ff 100644 --- a/core/test/integration/api/api_db_spec.js +++ b/core/test/integration/api/api_db_spec.js @@ -5,8 +5,8 @@ var testUtils = require('../../utils'), // Stuff we are testing permissions = require('../../../server/permissions'), DataGenerator = require('../../utils/fixtures/data-generator'), - dbAPI = require('../../../server/api/db'); - TagsAPI = require('../../../server/api/tags'); + dbAPI = require('../../../server/api/db'), + TagsAPI = require('../../../server/api/tags'), PostAPI = require('../../../server/api/posts'); describe('DB API', function () { diff --git a/core/test/integration/api/api_slugs_spec.js b/core/test/integration/api/api_slugs_spec.js new file mode 100644 index 0000000000..82dec22ea0 --- /dev/null +++ b/core/test/integration/api/api_slugs_spec.js @@ -0,0 +1,66 @@ +var testUtils = require('../../utils'), + should = require('should'), + + permissions = require('../../../server/permissions'), + slugAPI = require('../../../server/api/slugs'); + +describe('Slug API', function () { + + before(function (done) { + testUtils.clearData().then(function () { + done(); + }).catch(done); + }); + + beforeEach(function (done) { + testUtils.initData().then(function () { + return testUtils.insertDefaultFixtures(); + }).then(function () { + done(); + }).catch(done); + }); + + afterEach(function (done) { + testUtils.clearData().then(function () { + done(); + }).catch(done); + }); + + it('can generate post slug', function (done) { + permissions.init().then(function () { + return slugAPI.generate({ context: {user: 1}, type: 'post', title: 'A fancy Title'}); + }).then(function (results) { + should.exist(results); + testUtils.API.checkResponse(results, 'slugs'); + results.slugs.length.should.be.above(0); + testUtils.API.checkResponse(results.slugs[0], 'slug'); + results.slugs[0].slug.should.equal('a-fancy-title'); + done(); + }).catch(done); + }); + + it('can generate tag slug', function (done) { + permissions.init().then(function () { + return slugAPI.generate({ context: {user: 1}, type: 'tag', title: 'A fancy Title'}); + }).then(function (results) { + should.exist(results); + testUtils.API.checkResponse(results, 'slugs'); + results.slugs.length.should.be.above(0); + testUtils.API.checkResponse(results.slugs[0], 'slug'); + results.slugs[0].slug.should.equal('a-fancy-title'); + done(); + }).catch(done); + }); + + it('reject unknown type', function (done) { + permissions.init().then(function () { + return slugAPI.generate({ context: {user: 1}, type: 'unknown type', title: 'A fancy Title'}); + }).then(function () { + done(new Error('Generate a slug for a unknown type is not rejected.')); + }, function (error) { + error.type.should.eql('BadRequestError'); + done(); + }); + }); + +}); \ No newline at end of file diff --git a/core/test/utils/api.js b/core/test/utils/api.js index b0321cd406..e6cbcdb8b8 100644 --- a/core/test/utils/api.js +++ b/core/test/utils/api.js @@ -18,7 +18,9 @@ var url = require('url'), user: ['id', 'uuid', 'name', 'slug', 'email', 'image', 'cover', 'bio', 'website', 'location', 'accessibility', 'status', 'language', 'meta_title', 'meta_description', 'last_login', 'created_at', 'created_by', 'updated_at', 'updated_by'], - notification: ['type', 'message', 'status', 'id', 'dismissable', 'location'] + notification: ['type', 'message', 'status', 'id', 'dismissable', 'location'], + slugs: ['slugs'], + slug: ['slug'] }; function getApiQuery(route) {