Merge pull request #3223 from ErisDS/issue-3187

Move post slug endpoint & add endpoints for users
This commit is contained in:
Hannah Wolfe 2014-07-09 21:21:01 +01:00
commit d2cc9e5046
7 changed files with 364 additions and 228 deletions

View File

@ -49,10 +49,10 @@ init = function () {
* @return {Promise(String)} Resolves to header string
*/
cacheInvalidationHeader = function (req, result) {
var parsedUrl = req._parsedUrl.pathname.replace(/\/$/, '').split('/'),
var parsedUrl = req._parsedUrl.pathname.replace(/^\/|\/$/g, '').split('/'),
method = req.method,
endpoint = parsedUrl[4],
id = parsedUrl[5],
endpoint = parsedUrl[0],
id = parsedUrl[1],
cacheInvalidate,
jsonResult = result.toJSON ? result.toJSON() : result,
post,
@ -104,16 +104,15 @@ locationHeader = function (req, result) {
location,
post,
notification,
parsedUrl = req._parsedUrl.pathname.replace(/\/$/, '').split('/'),
endpoint = parsedUrl[4];
endpoint = req._parsedUrl.pathname;
if (req.method === 'POST') {
if (result.hasOwnProperty('posts')) {
post = result.posts[0];
location = apiRoot + '/posts/' + post.id + '/?status=' + post.status;
} else if (endpoint === 'notifications') {
} else if (endpoint === '/notifications/') {
notification = result.notifications;
location = apiRoot + '/notifications/' + notification[0].id;
location = apiRoot + endpoint + notification[0].id;
}
}

View File

@ -58,7 +58,7 @@ users = {
* @returns {Promise(User)} User
*/
read: function read(options) {
var attrs = ['id'],
var attrs = ['id', 'slug', 'email'],
data = _.pick(options, attrs);
options = _.omit(options, attrs);
@ -250,7 +250,7 @@ users = {
/**
* ### Change Password
* @param {password} object
* @param {password} object
* @param {{context}} options
* @returns {Promise(password}} success message
*/

View File

@ -288,7 +288,6 @@ module.exports = function (server) {
// ### Caching
expressServer.use(middleware.cacheControl('public'));
expressServer.use(subdir + '/api/', middleware.cacheControl('private'));
expressServer.use(subdir + '/ghost/', middleware.cacheControl('private'));
@ -304,7 +303,7 @@ module.exports = function (server) {
// ### Routing
// Set up API routes
expressServer.use(subdir, routes.api(middleware));
expressServer.use(subdir + routes.apiBaseUri, routes.api(middleware));
// Set up Admin routes
expressServer.use(subdir, routes.admin(middleware));

View File

@ -1,46 +1,71 @@
// # API routes
var express = require('express'),
api = require('../api'),
apiRoutes;
apiRoutes,
resources;
resources = {
posts: function (router) {
router.get('/posts', api.http(api.posts.browse));
router.post('/posts', api.http(api.posts.add));
router.get('/posts/:id', api.http(api.posts.read));
router.get('/posts/slug/:slug', api.http(api.posts.read));
router.put('/posts/:id', api.http(api.posts.edit));
router.del('/posts/:id', api.http(api.posts.destroy));
}
};
apiRoutes = function (middleware) {
var router = express.Router();
// alias delete with del
router.del = router.delete;
// ## Posts
router.get('/ghost/api/v0.1/posts', api.http(api.posts.browse));
router.post('/ghost/api/v0.1/posts', api.http(api.posts.add));
router.get('/ghost/api/v0.1/posts/:id(\\d+)', api.http(api.posts.read));
router.get('/ghost/api/v0.1/posts/:slug([a-z-]+)', api.http(api.posts.read));
router.put('/ghost/api/v0.1/posts/:id', api.http(api.posts.edit));
router['delete']('/ghost/api/v0.1/posts/:id', api.http(api.posts.destroy));
router.get('/posts', api.http(api.posts.browse));
router.post('/posts', api.http(api.posts.add));
router.get('/posts/:id', api.http(api.posts.read));
router.get('/posts/slug/:slug', api.http(api.posts.read));
router.put('/posts/:id', api.http(api.posts.edit));
router.del('/posts/:id', api.http(api.posts.destroy));
// ## Settings
router.get('/ghost/api/v0.1/settings/', api.http(api.settings.browse));
router.get('/ghost/api/v0.1/settings/:key/', api.http(api.settings.read));
router.put('/ghost/api/v0.1/settings/', api.http(api.settings.edit));
router.get('/settings', api.http(api.settings.browse));
router.get('/settings/:key', api.http(api.settings.read));
router.put('/settings', api.http(api.settings.edit));
// ## Users
router.get('/ghost/api/v0.1/users/', api.http(api.users.browse));
router.get('/ghost/api/v0.1/users/:id/', api.http(api.users.read));
router.put('/ghost/api/v0.1/users/password/', api.http(api.users.changePassword));
router.put('/ghost/api/v0.1/users/:id/', api.http(api.users.edit));
router.post('/ghost/api/v0.1/users/', api.http(api.users.invite));
router['delete']('/ghost/api/v0.1/users/:id/', api.http(api.users.destroy));
router.get('/users', api.http(api.users.browse));
router.get('/users/:id', api.http(api.users.read));
router.get('/users/slug/:slug', api.http(api.users.read));
router.get('/users/email/:email', api.http(api.users.read));
router.put('/users/password', api.http(api.users.changePassword));
router.put('/users/:id', api.http(api.users.edit));
router.post('/users', api.http(api.users.invite));
router.del('/users/:id', api.http(api.users.destroy));
// ## Tags
router.get('/ghost/api/v0.1/tags/', api.http(api.tags.browse));
router.get('/tags', api.http(api.tags.browse));
// ## Slugs
router.get('/slugs/:type/:name', api.http(api.slugs.generate));
// ## Themes
router.get('/ghost/api/v0.1/themes/', api.http(api.themes.browse));
router.put('/ghost/api/v0.1/themes/:name', api.http(api.themes.edit));
router.get('/themes', api.http(api.themes.browse));
router.put('/themes/:name', api.http(api.themes.edit));
// ## Notifications
router.get('/ghost/api/v0.1/notifications/', api.http(api.notifications.browse));
router.post('/ghost/api/v0.1/notifications/', api.http(api.notifications.add));
router['delete']('/ghost/api/v0.1/notifications/:id', api.http(api.notifications.destroy));
router.get('/notifications', api.http(api.notifications.browse));
router.post('/notifications', api.http(api.notifications.add));
router.del('/notifications/:id', api.http(api.notifications.destroy));
// ## DB
router.get('/ghost/api/v0.1/db/', api.http(api.db.exportContent));
router.post('/ghost/api/v0.1/db/', middleware.busboy, api.http(api.db.importContent));
router['delete']('/ghost/api/v0.1/db/', api.http(api.db.deleteAllContent));
router.get('/db', api.http(api.db.exportContent));
router.post('/db', middleware.busboy, api.http(api.db.importContent));
router.del('/db', api.http(api.db.deleteAllContent));
// ## Mail
router.post('/ghost/api/v0.1/mail', api.http(api.mail.send));
router.post('/ghost/api/v0.1/mail/test', function (req, res) {
router.post('/mail', api.http(api.mail.send));
router.post('/mail/test', function (req, res) {
api.settings.read('email').then(function (result) {
// attach the to: address to the request body so that it is available
// to the http api handler
@ -51,15 +76,13 @@ apiRoutes = function (middleware) {
api.http(api.mail.sendTest)(req, res);
});
});
// ## Slugs
router.get('/ghost/api/v0.1/slugs/:type/:name', api.http(api.slugs.generate));
// ## Authentication
router.post('/ghost/api/v0.1/authentication/passwordreset', api.http(api.authentication.generateResetToken));
router.put('/ghost/api/v0.1/authentication/passwordreset', api.http(api.authentication.resetPassword));
router.post('/ghost/api/v0.1/authentication/invitation', api.http(api.authentication.acceptInvitation));
router.post('/ghost/api/v0.1/authentication/token',
router.post('/authentication/passwordreset', api.http(api.authentication.generateResetToken));
router.put('/authentication/passwordreset', api.http(api.authentication.resetPassword));
router.post('/authentication/invitation', api.http(api.authentication.acceptInvitation));
router.post('/authentication/token',
middleware.addClientSecret,
middleware.authenticateClient,
middleware.generateAccessToken

View File

@ -3,6 +3,7 @@ var api = require('./api'),
frontend = require('./frontend');
module.exports = {
apiBaseUri: '/ghost/api/v0.1/',
api: api,
admin: admin,
frontend: frontend

View File

@ -201,7 +201,7 @@ describe('Post API', function () {
});
it('can retrieve a post by slug', function (done) {
request.get(testUtils.API.getApiQuery('posts/welcome-to-ghost/'))
request.get(testUtils.API.getApiQuery('posts/slug/welcome-to-ghost/'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.end(function (err, res) {
@ -542,7 +542,7 @@ describe('Post API', function () {
return done(err);
}
var unpublishedPost = res.body;
// Unpublishing a post should send x-cache-invalidate headers
_.has(res.headers, 'x-cache-invalidate').should.equal(true);

View File

@ -30,7 +30,7 @@ describe('User API', function () {
})
.then(function () {
request.post('/ghost/api/v0.1/authentication/token/')
.send({ grant_type: "password", username: user.email, password: user.password, client_id: "ghost-admin"})
.send({ grant_type: 'password', username: user.email, password: user.password, client_id: 'ghost-admin'})
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
@ -53,202 +53,316 @@ describe('User API', function () {
httpServer.close();
});
it('returns dates in ISO 8601 format', function (done) {
request.get(testUtils.API.getApiQuery('users/'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
describe('Browse', function () {
var jsonResponse = res.body;
jsonResponse.users.should.exist;
testUtils.API.checkResponse(jsonResponse, 'users');
it('returns dates in ISO 8601 format', function (done) {
request.get(testUtils.API.getApiQuery('users/'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
jsonResponse.users.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']);
var jsonResponse = res.body;
jsonResponse.users.should.exist;
testUtils.API.checkResponse(jsonResponse, 'users');
testUtils.API.isISO8601(jsonResponse.users[0].last_login).should.be.true;
testUtils.API.isISO8601(jsonResponse.users[0].created_at).should.be.true;
testUtils.API.isISO8601(jsonResponse.users[0].updated_at).should.be.true;
jsonResponse.users.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']);
done();
});
testUtils.API.isISO8601(jsonResponse.users[0].last_login).should.be.true;
testUtils.API.isISO8601(jsonResponse.users[0].created_at).should.be.true;
testUtils.API.isISO8601(jsonResponse.users[0].updated_at).should.be.true;
done();
});
});
it('can retrieve all users', function (done) {
request.get(testUtils.API.getApiQuery('users/'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
jsonResponse.users.should.exist;
testUtils.API.checkResponse(jsonResponse, 'users');
jsonResponse.users.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']);
done();
});
});
});
it('can retrieve all users', function (done) {
request.get(testUtils.API.getApiQuery('users/'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
describe('Read', function () {
it('can retrieve a user by "me"', function (done) {
request.get(testUtils.API.getApiQuery('users/me/'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
jsonResponse.users.should.exist;
testUtils.API.checkResponse(jsonResponse, 'users');
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
jsonResponse.users.should.exist;
testUtils.API.checkResponse(jsonResponse, 'users');
jsonResponse.users.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']);
done();
});
jsonResponse.users.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']);
done();
});
});
it('can retrieve a user by id', function (done) {
request.get(testUtils.API.getApiQuery('users/1/'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
jsonResponse.users.should.exist;
testUtils.API.checkResponse(jsonResponse, 'users');
jsonResponse.users.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']);
done();
});
});
it('can retrieve a user by slug', function (done) {
request.get(testUtils.API.getApiQuery('users/slug/joe-blogs/'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
jsonResponse.users.should.exist;
testUtils.API.checkResponse(jsonResponse, 'users');
jsonResponse.users.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']);
done();
});
});
it('can retrieve a user by email', function (done) {
request.get(testUtils.API.getApiQuery('users/email/jbloggs%40example.com/'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
jsonResponse.users.should.exist;
testUtils.API.checkResponse(jsonResponse, 'users');
jsonResponse.users.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']);
done();
});
});
it('can retrieve a user with role', function (done) {
request.get(testUtils.API.getApiQuery('users/me/?include=roles'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
jsonResponse.users.should.exist;
testUtils.API.checkResponse(jsonResponse, 'users');
jsonResponse.users.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']);
testUtils.API.checkResponse(jsonResponse.users[0].roles[0], 'role');
done();
});
});
it('can retrieve a user with role and permissions', function (done) {
request.get(testUtils.API.getApiQuery('users/me/?include=roles,roles.permissions'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
jsonResponse.users.should.exist;
testUtils.API.checkResponse(jsonResponse, 'users');
jsonResponse.users.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']);
testUtils.API.checkResponse(jsonResponse.users[0].roles[0], 'role', ['permissions']);
testUtils.API.checkResponse(jsonResponse.users[0].roles[0].permissions[0], 'permission');
done();
});
});
it('can retrieve a user by slug with role and permissions', function (done) {
request.get(testUtils.API.getApiQuery('users/slug/joe-blogs/?include=roles,roles.permissions'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
jsonResponse.users.should.exist;
testUtils.API.checkResponse(jsonResponse, 'users');
jsonResponse.users.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']);
testUtils.API.checkResponse(jsonResponse.users[0].roles[0], 'role', ['permissions']);
testUtils.API.checkResponse(jsonResponse.users[0].roles[0].permissions[0], 'permission');
done();
});
});
it('can\'t retrieve non existent user by id', function (done) {
request.get(testUtils.API.getApiQuery('users/99/'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.expect(404)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
jsonResponse.should.exist;
jsonResponse.errors.should.exist;
testUtils.API.checkResponseValue(jsonResponse.errors[0], ['message', 'type']);
done();
});
});
it('can\'t retrieve non existent user by slug', function (done) {
request.get(testUtils.API.getApiQuery('users/slug/blargh/'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.expect(404)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
jsonResponse.should.exist;
jsonResponse.errors.should.exist;
testUtils.API.checkResponseValue(jsonResponse.errors[0], ['message', 'type']);
done();
});
});
});
describe('Edit', function () {
it('can edit a user', function (done) {
request.get(testUtils.API.getApiQuery('users/me/'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) {
return done(err);
}
it('can retrieve a user', function (done) {
request.get(testUtils.API.getApiQuery('users/me/'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
var jsonResponse = res.body,
changedValue = 'joe-bloggs.ghost.org',
dataToSend;
jsonResponse.users[0].should.exist;
testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']);
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
jsonResponse.users.should.exist;
testUtils.API.checkResponse(jsonResponse, 'users');
dataToSend = { users: [
{website: changedValue}
]};
jsonResponse.users.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']);
done();
});
});
request.put(testUtils.API.getApiQuery('users/me/'))
.set('Authorization', 'Bearer ' + accesstoken)
.send(dataToSend)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
it('can retrieve a user with role', function (done) {
request.get(testUtils.API.getApiQuery('users/me/?include=roles'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
var putBody = res.body;
res.headers['x-cache-invalidate'].should.eql('/*');
putBody.users[0].should.exist;
putBody.users[0].website.should.eql(changedValue);
putBody.users[0].email.should.eql(jsonResponse.users[0].email);
testUtils.API.checkResponse(putBody.users[0], 'user', ['roles']);
done();
});
});
});
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
jsonResponse.users.should.exist;
testUtils.API.checkResponse(jsonResponse, 'users');
it('can\'t edit a user with invalid accesstoken', function (done) {
request.get(testUtils.API.getApiQuery('users/me/'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) {
return done(err);
}
jsonResponse.users.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']);
testUtils.API.checkResponse(jsonResponse.users[0].roles[0], 'role');
done();
});
});
var jsonResponse = res.body,
changedValue = 'joe-bloggs.ghost.org';
jsonResponse.users[0].should.exist;
jsonResponse.users[0].website = changedValue;
it('can retrieve a user with role and permissions', function (done) {
request.get(testUtils.API.getApiQuery('users/me/?include=roles,roles.permissions'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
request.put(testUtils.API.getApiQuery('users/me/'))
.set('Authorization', 'Bearer ' + 'invalidtoken')
.send(jsonResponse)
.expect(401)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
jsonResponse.users.should.exist;
testUtils.API.checkResponse(jsonResponse, 'users');
done();
});
jsonResponse.users.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']);
testUtils.API.checkResponse(jsonResponse.users[0].roles[0], 'role', ['permissions']);
testUtils.API.checkResponse(jsonResponse.users[0].roles[0].permissions[0], 'permission');
done();
});
});
it('can\'t retrieve non existent user', function (done) {
request.get(testUtils.API.getApiQuery('users/99/'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.expect(404)
.end(function (err, res) {
if (err) {
return done(err);
}
should.not.exist(res.headers['x-cache-invalidate']);
var jsonResponse = res.body;
jsonResponse.should.exist;
jsonResponse.errors.should.exist;
testUtils.API.checkResponseValue(jsonResponse.errors[0], ['message', 'type']);
done();
});
});
it('can edit a user', function (done) {
request.get(testUtils.API.getApiQuery('users/me/'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) {
return done(err);
}
var jsonResponse = res.body,
changedValue = 'joe-bloggs.ghost.org',
dataToSend;
jsonResponse.users[0].should.exist;
testUtils.API.checkResponse(jsonResponse.users[0], 'user', ['roles']);
dataToSend = { users: [{website: changedValue}]};
request.put(testUtils.API.getApiQuery('users/me/'))
.set('Authorization', 'Bearer ' + accesstoken)
.send(dataToSend)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
return done(err);
}
var putBody = res.body;
res.headers['x-cache-invalidate'].should.eql('/*');
putBody.users[0].should.exist;
putBody.users[0].website.should.eql(changedValue);
putBody.users[0].email.should.eql(jsonResponse.users[0].email);
testUtils.API.checkResponse(putBody.users[0], 'user', ['roles']);
done();
});
});
});
it('can\'t edit a user with invalid accesstoken', function (done) {
request.get(testUtils.API.getApiQuery('users/me/'))
.set('Authorization', 'Bearer ' + accesstoken)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) {
return done(err);
}
var jsonResponse = res.body,
changedValue = 'joe-bloggs.ghost.org';
jsonResponse.users[0].should.exist;
jsonResponse.users[0].website = changedValue;
request.put(testUtils.API.getApiQuery('users/me/'))
.set('Authorization', 'Bearer ' + 'invalidtoken')
.send(jsonResponse)
.expect(401)
.end(function (err, res) {
if (err) {
return done(err);
}
done();
});
});
});
});
});
});