Update slug API to work with additional types

Closes #2866
-update slug API to handle users and apps in addition to
 posts and tags
-update existing tests
-add new functional tests for slug endpoint on http api
This commit is contained in:
Jason Williams 2014-06-04 05:47:16 +00:00
parent 1ae98df995
commit bb4a0a3540
5 changed files with 232 additions and 23 deletions

View File

@ -9,7 +9,9 @@ var canThis = require('../permissions').canThis,
// `allowedTypes` is used to define allowed slug types and map them against its model class counterpart
allowedTypes = {
post: dataProvider.Post,
tag: dataProvider.Tag
tag: dataProvider.Tag,
user: dataProvider.User,
app: dataProvider.App
};
/**
@ -21,10 +23,9 @@ slugs = {
/**
* ## Generate Slug
* Create a unique slug for a given post title
* TODO: make it generic for all objects: post, tag, user
* Create a unique slug for the given type and its name
*
* @param {{type (required), title (required), context, transacting}} options
* @param {{type (required), name (required), context, transacting}} options
* @returns {Promise(String)} Unique string
*/
generate: function (options) {
@ -35,14 +36,18 @@ slugs = {
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) {
return dataProvider.Base.Model.generateSlug(allowedTypes[options.type], options.name, {status: 'all'}).then(function (slug) {
if (!slug) {
return when.reject(new errors.InternalServerError('Could not generate slug.'));
}
return { slugs: [{ slug: slug }] };
});
}, function () {
}).catch(function (err) {
if (err) {
return when.reject(err);
}
return when.reject(new errors.NoPermissionError('You do not have permission to generate a slug.'));
});
}

View File

@ -288,7 +288,7 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
var slug,
slugTryCount = 1,
baseName = Model.prototype.tableName.replace(/s$/, ''),
// Look for a post with a matching slug, append an incrementing number if so
// Look for a matching slug, append an incrementing number if so
checkIfSlugExists;
checkIfSlugExists = function (slugToFind) {
@ -339,10 +339,10 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
slug = slug.charAt(slug.length - 1) === '-' ? slug.substr(0, slug.length - 1) : slug;
// Check the filtered slug doesn't match any of the reserved keywords
slug = /^(ghost|ghost\-admin|admin|wp\-admin|wp\-login|dashboard|logout|login|signin|signup|signout|register|archive|archives|category|categories|tag|tags|page|pages|post|posts|public|user|users|rss|feed)$/g
slug = /^(ghost|ghost\-admin|admin|wp\-admin|wp\-login|dashboard|logout|login|signin|signup|signout|register|archive|archives|category|categories|tag|tags|page|pages|post|posts|public|user|users|rss|feed|app|apps)$/g
.test(slug) ? slug + '-' + baseName : slug;
//if slug is empty after trimming use "post"
//if slug is empty after trimming use the model name
if (!slug) {
slug = baseName;
}

View File

@ -36,7 +36,7 @@ apiRoutes = function (server) {
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));
server.get('/ghost/api/v0.1/slugs/:type/:name', api.http(api.slugs.generate));
};
module.exports = apiRoutes;

View File

@ -0,0 +1,181 @@
/*global describe, it, before, after */
var supertest = require('supertest'),
express = require('express'),
should = require('should'),
_ = require('lodash'),
testUtils = require('../../../utils'),
ghost = require('../../../../../core'),
httpServer,
request,
agent;
describe('Slug API', function () {
var user = testUtils.DataGenerator.forModel.users[0],
csrfToken = '';
before(function (done) {
var app = express();
ghost({ app: app }).then(function (_httpServer) {
httpServer = _httpServer;
request = supertest.agent(app);
testUtils.clearData()
.then(function () {
return testUtils.initData();
})
.then(function () {
return testUtils.insertDefaultFixtures();
})
.then(function () {
request.get('/ghost/signin/')
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
var pattern_meta = /<meta.*?name="csrf-param".*?content="(.*?)".*?>/i;
pattern_meta.should.exist;
csrfToken = res.text.match(pattern_meta)[1];
process.nextTick(function() {
request.post('/ghost/signin/')
.set('X-CSRF-Token', csrfToken)
.send({email: user.email, password: user.password})
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
request.saveCookies(res);
request.get('/ghost/')
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
csrfToken = res.text.match(pattern_meta)[1];
done();
});
});
});
});
}).catch(done);
}).catch(function (e) {
console.log('Ghost Error: ', e);
console.log(e.stack);
});
});
after(function () {
httpServer.close();
});
it('should be able to get a post slug', function (done) {
request.get(testUtils.API.getApiQuery('slugs/post/a post title/'))
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
res.should.be.json;
var jsonResponse = res.body;
jsonResponse.should.exist;
jsonResponse.slugs.should.exist;
jsonResponse.slugs.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.slugs[0], 'slug');
jsonResponse.slugs[0].slug.should.equal('a-post-title');
done();
});
});
it('should be able to get a tag slug', function (done) {
request.get(testUtils.API.getApiQuery('slugs/post/atag/'))
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
res.should.be.json;
var jsonResponse = res.body;
jsonResponse.should.exist;
jsonResponse.slugs.should.exist;
jsonResponse.slugs.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.slugs[0], 'slug');
jsonResponse.slugs[0].slug.should.equal('atag');
done();
});
});
it('should be able to get a user slug', function (done) {
request.get(testUtils.API.getApiQuery('slugs/user/user name/'))
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
res.should.be.json;
var jsonResponse = res.body;
jsonResponse.should.exist;
jsonResponse.slugs.should.exist;
jsonResponse.slugs.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.slugs[0], 'slug');
jsonResponse.slugs[0].slug.should.equal('user-name');
done();
});
});
it('should be able to get an app slug', function (done) {
request.get(testUtils.API.getApiQuery('slugs/app/cool app/'))
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
res.should.be.json;
var jsonResponse = res.body;
jsonResponse.should.exist;
jsonResponse.slugs.should.exist;
jsonResponse.slugs.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.slugs[0], 'slug');
jsonResponse.slugs[0].slug.should.equal('cool-app');
done();
});
});
it('should not be able to get a slug for an unknown type', function (done) {
request.get(testUtils.API.getApiQuery('slugs/unknown/who knows/'))
.expect(400)
.end(function (err, res) {
if (err) {
return done(err);
}
res.should.be.json;
var jsonResponse = res.body;
jsonResponse.should.not.exist;
done();
});
});
});

View File

@ -15,6 +15,8 @@ describe('Slug API', function () {
beforeEach(function (done) {
testUtils.initData().then(function () {
return testUtils.insertDefaultFixtures();
}).then(function () {
return permissions.init();
}).then(function () {
done();
}).catch(done);
@ -27,9 +29,8 @@ describe('Slug API', function () {
});
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) {
slugAPI.generate({ context: { user: 1 }, type: 'post', name: 'A fancy Title' })
.then(function (results) {
should.exist(results);
testUtils.API.checkResponse(results, 'slugs');
results.slugs.length.should.be.above(0);
@ -40,9 +41,8 @@ describe('Slug API', function () {
});
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) {
slugAPI.generate({ context: { user: 1 }, type: 'tag', name: 'A fancy Title' })
.then(function (results) {
should.exist(results);
testUtils.API.checkResponse(results, 'slugs');
results.slugs.length.should.be.above(0);
@ -52,13 +52,36 @@ describe('Slug API', function () {
}).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');
it('can generate user slug', function (done) {
slugAPI.generate({ context: { user: 1 }, type: 'tag', name: 'user name' })
.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('user-name');
done();
}).catch(done);
});
it('can generate app slug', function (done) {
slugAPI.generate({ context: { user: 1 }, type: 'tag', name: 'app name' })
.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('app-name');
done();
}).catch(done);
});
it('rejects unknown types', function (done) {
slugAPI.generate({ context: { user: 1 }, type: 'unknown type', name: 'A fancy Title' })
.then(function () {
done(new Error('Generate a slug for an unknown type is not rejected.'));
}).catch(function (error) {
error.type.should.equal('BadRequestError');
done();
}).catch(done);
});