This repository has been archived by the owner on Mar 7, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #489 from sgmap/641-profile-snapshot-api
[#489] [FEATURE] création d'un instantané du profil de compétence (Api) (US-641)
- Loading branch information
Showing
18 changed files
with
1,147 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
const TABLE_NAME = 'snapshots'; | ||
|
||
exports.up = function(knex) { | ||
|
||
function table(t) { | ||
t.increments().primary(); | ||
t.string('organizationId').unsigned().references('organizations.id'); | ||
t.string('userId').unsigned().references('users.id'); | ||
t.string('score'); | ||
t.json('profile').notNullable(); | ||
t.dateTime('createdAt').notNullable().defaultTo(knex.fn.now()); | ||
t.dateTime('updatedAt').notNullable().defaultTo(knex.fn.now()); | ||
} | ||
|
||
return knex.schema | ||
.createTable(TABLE_NAME, table) | ||
.then(() => { | ||
console.log(`${TABLE_NAME} table is created!`); | ||
}); | ||
}; | ||
|
||
exports.down = function(knex) { | ||
return knex.schema | ||
.dropTable(TABLE_NAME) | ||
.then(() => { | ||
console.log(`${TABLE_NAME} table was dropped!`); | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
const snapshotController = require('./snapshot-controller'); | ||
exports.register = function(server, options, next) { | ||
|
||
server.route([ | ||
{ | ||
method: 'POST', | ||
path: '/api/snapshots', | ||
config: { | ||
handler: snapshotController.create, tags: ['api'] | ||
} | ||
} | ||
]); | ||
|
||
return next(); | ||
}; | ||
|
||
exports.register.attributes = { | ||
name: 'snapshots-api', | ||
version: '1.0.0' | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
const authorizationToken = require('../../../lib/infrastructure/validators/jsonwebtoken-verify'); | ||
const validationErrorSerializer = require('../../infrastructure/serializers/jsonapi/validation-error-serializer'); | ||
const UserRepository = require('../../../lib/infrastructure/repositories/user-repository'); | ||
const OrganizationRepository = require('../../../lib/infrastructure/repositories/organization-repository'); | ||
const snapshotSerializer = require('../../../lib/infrastructure/serializers/jsonapi/snapshot-serializer'); | ||
const profileSerializer = require('../../../lib/infrastructure/serializers/jsonapi/profile-serializer'); | ||
const SnapshotService = require('../../../lib/domain/services/snapshot-service'); | ||
const profileService = require('../../domain/services/profile-service'); | ||
const logger = require('../../../lib/infrastructure/logger'); | ||
const { InvalidTokenError, NotFoundError, InvaliOrganizationIdError } = require('../../domain/errors'); | ||
|
||
function _assertThatOrganizationExists(organizationId) { | ||
return OrganizationRepository.isOrganizationIdExist(organizationId) | ||
.then((isOrganizationExist) => { | ||
if(!isOrganizationExist) { | ||
throw new InvaliOrganizationIdError(); | ||
} | ||
}); | ||
} | ||
|
||
const _replyErrorWithMessage = function(reply, errorMessage, statusCode) { | ||
reply(validationErrorSerializer.serialize(_handleWhenInvalidAuthorization(errorMessage))).code(statusCode); | ||
}; | ||
|
||
function _handleWhenInvalidAuthorization(errorMessage) { | ||
return { | ||
data: { | ||
authorization: [errorMessage] | ||
} | ||
}; | ||
} | ||
|
||
function _extractOrganizationId(request) { | ||
return request.hasOwnProperty('payload') && request.payload.data && request.payload.data.attributes['organization-id'] || ''; | ||
} | ||
|
||
function _hasAnAtuhorizationHeaders(request) { | ||
return request && request.hasOwnProperty('headers') && request.headers.hasOwnProperty('authorization'); | ||
} | ||
|
||
function _replyError(err, reply) { | ||
if(err instanceof InvalidTokenError) { | ||
return _replyErrorWithMessage(reply, 'Le token n’est pas valide', 401); | ||
} | ||
|
||
if(err instanceof NotFoundError) { | ||
return _replyErrorWithMessage(reply, 'Cet utilisateur est introuvable', 422); | ||
} | ||
|
||
if(err instanceof InvaliOrganizationIdError) { | ||
return _replyErrorWithMessage(reply, 'Cette organisation n’existe pas', 422); | ||
} | ||
logger.error(err); | ||
return _replyErrorWithMessage(reply, 'Une erreur est survenue lors de la création de l’instantané', 500); | ||
} | ||
|
||
function create(request, reply) { | ||
|
||
if(!_hasAnAtuhorizationHeaders(request)) { | ||
return _replyErrorWithMessage(reply, 'Le token n’est pas valide', 401); | ||
} | ||
|
||
const token = request.headers.authorization; | ||
const organizationId = _extractOrganizationId(request); | ||
|
||
return authorizationToken | ||
.verify(token) | ||
.then(UserRepository.findUserById) | ||
.then((foundUser) => _assertThatOrganizationExists(organizationId).then(() => foundUser)) | ||
.then(({ id }) => profileService.getByUserId(id)) | ||
.then((profile) => profileSerializer.serialize(profile)) | ||
.then((profile) => SnapshotService.create({ organizationId, profile })) | ||
.then((snapshotId) => snapshotSerializer.serialize({ id: snapshotId })) | ||
.then(snapshotSerialized => reply(snapshotSerialized).code(201)) | ||
.catch((err) => _replyError(err, reply)); | ||
} | ||
|
||
module.exports = { create }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
const Bookshelf = require('../../../infrastructure/bookshelf'); | ||
const Organization = require('./organization'); | ||
const User = require('./user'); | ||
|
||
module.exports = Bookshelf.Model.extend({ | ||
tableName: 'snapshots', | ||
|
||
organizations() { | ||
return this.belongsTo(Organization); | ||
}, | ||
|
||
users() { | ||
return this.belongsTo(User); | ||
} | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
const snapshotRepository = require('../../../lib/infrastructure/repositories/snapshot-repository'); | ||
|
||
module.exports = { | ||
create(snapshot) { | ||
const snapshotRaw = { | ||
organizationId: snapshot.organizationId, | ||
userId: snapshot.profile.data.id, | ||
score: snapshot.profile.data.attributes['total-pix-score'], | ||
profile: JSON.stringify(snapshot.profile) | ||
}; | ||
|
||
return snapshotRepository | ||
.save(snapshotRaw) | ||
.then((snapshot) => snapshot.get('id')); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
const Snapshot = require('../../domain/models/data/snapshot'); | ||
|
||
module.exports = { | ||
save(snapshotRawData) { | ||
return new Snapshot(snapshotRawData).save(); | ||
} | ||
}; |
15 changes: 15 additions & 0 deletions
15
api/lib/infrastructure/serializers/jsonapi/snapshot-serializer.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
const JSONAPISerializer = require('jsonapi-serializer').Serializer; | ||
|
||
class SnapshotSerializer { | ||
serialize(snapshot) { | ||
return new JSONAPISerializer('snapshots', { | ||
attributes: ['id'], | ||
transform(snapshot) { | ||
snapshot.id = snapshot.id.toString(); | ||
return snapshot; | ||
} | ||
}).serialize(snapshot); | ||
} | ||
} | ||
|
||
module.exports = new SnapshotSerializer(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
136 changes: 136 additions & 0 deletions
136
api/tests/acceptance/application/snapshot-controller_test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
const faker = require('faker'); | ||
const bcrypt = require('bcrypt'); | ||
const { describe, it, after, before, expect, afterEach, beforeEach, knex, sinon } = require('../../test-helper'); | ||
const authorizationToken = require('../../../lib/infrastructure/validators/jsonwebtoken-verify'); | ||
const profileService = require('../../../lib/domain/services/profile-service'); | ||
const User = require('../../../lib/domain/models/data/user'); | ||
const server = require('../../../server'); | ||
|
||
describe('Acceptance | Controller | snapshot-controller', function() { | ||
|
||
let userId; | ||
let organizationId; | ||
const userPassword = bcrypt.hashSync('A124B2C3#!', 1); | ||
const fakeUser = new User({ | ||
id: 'user_id', | ||
'firstName': faker.name.firstName(), | ||
'lastName': faker.name.lastName(), | ||
'email': faker.internet.email() | ||
}); | ||
const fakeBuildedProfile = { | ||
user: fakeUser, | ||
competences: [{ | ||
id: 'recCompA', | ||
name: 'competence-name-1', | ||
index: '1.1', | ||
areaId: 'recAreaA', | ||
level: -1, | ||
courseId: 'recBxPAuEPlTgt72q11' | ||
}, | ||
{ | ||
id: 'recCompB', | ||
name: 'competence-name-2', | ||
index: '1.2', | ||
areaId: 'recAreaB', | ||
level: -1, | ||
courseId: 'recBxPAuEPlTgt72q99' | ||
}], | ||
areas: [{ id: 'recAreaA', name: 'domaine-name-1' }, { id: 'recAreaB', name: 'domaine-name-2' }], | ||
organizations: [] | ||
}; | ||
|
||
const inserted_user = { | ||
firstName: faker.name.firstName(), | ||
lastName: faker.name.lastName(), | ||
email: faker.internet.email(), | ||
password: userPassword, | ||
cgu: true | ||
}; | ||
|
||
const inserted_organization = { | ||
name: 'The name of the organization', | ||
email: 'organization@email.com', | ||
type: 'PRO' | ||
}; | ||
|
||
before(() => { | ||
return knex.migrate.latest() | ||
.then(() => { | ||
return knex.seed.run(); | ||
}).then(() => { | ||
return knex('users').insert(inserted_user); | ||
}).then((result) => { | ||
userId = result.shift(); | ||
inserted_organization['userId'] = userId; | ||
return knex('organizations').insert(inserted_organization); | ||
}).then((organization) => { | ||
organizationId = organization.shift(); | ||
}); | ||
}); | ||
|
||
after(function(done) { | ||
server.stop(done); | ||
}); | ||
|
||
describe('POST /api/snapshots', function() { | ||
|
||
let payload; | ||
let options; | ||
let injectPromise; | ||
|
||
beforeEach(() => { | ||
payload = { | ||
data: { | ||
attributes: { | ||
'organization-id': organizationId | ||
} | ||
} | ||
}; | ||
|
||
options = { | ||
method: 'POST', | ||
url: '/api/snapshots', | ||
payload | ||
}; | ||
|
||
options['headers'] = { authorization: 'VALID_TOKEN' }; | ||
sinon.stub(authorizationToken, 'verify').resolves(userId); | ||
sinon.stub(profileService, 'getByUserId').resolves(fakeBuildedProfile); | ||
injectPromise = server.inject(options); | ||
}); | ||
|
||
afterEach(() => { | ||
authorizationToken.verify.restore(); | ||
profileService.getByUserId.restore(); | ||
return knex('snapshots').delete(); | ||
}); | ||
|
||
it('should return 201 HTTP status code', () => { | ||
// When | ||
return injectPromise.then((response) => { | ||
// then | ||
expect(response.statusCode).to.equal(201); | ||
expect(response.result.data.id).to.exist; | ||
}); | ||
}); | ||
|
||
describe('when creating with a wrong payload', () => { | ||
|
||
it('should return 422 HTTP status code', () => { | ||
// Given | ||
payload.data.attributes['organization-id'] = null; | ||
|
||
// Then | ||
const creatingSnapshotWithWrongParams = server.inject(options); | ||
|
||
// Then | ||
return creatingSnapshotWithWrongParams.then((response) => { | ||
const parsedResponse = JSON.parse(response.payload); | ||
expect(parsedResponse.errors[0].detail).to.equal('Cette organisation n’existe pas'); | ||
expect(response.statusCode).to.equal(422); | ||
}); | ||
}); | ||
|
||
}); | ||
}); | ||
}); |
Oops, something went wrong.