Skip to content
This repository has been archived by the owner on Mar 7, 2023. It is now read-only.

Commit

Permalink
[#457] [FEATURE] Ajout du score et du niveau par compétences sur le p…
Browse files Browse the repository at this point in the history
…rofil utilisateur côté API (US-574). (#457)

* [US-574] [FEATURE] Creating a default account with pix@contact.com / MyPix123#

* [US-574] [FIX] Fixing a test that was not compiling

* [US-574] [TECH] Assessement-controller tests improvements

* [US-574] [TECH] Moving the token creation method in a specific service

* [US-574] [TECH] Extracting methods to the token service

* [US-574] [TECH] Authorization header must 'Bearer' not 'bearer'

* [US-574] [FEATURE] An assessment is now linked to a user if connected

* [US-574] [TECH] Moving assessment-controller_test.js from controller folder to assessment folder

* [US-574] [FEATURE] Updating assessment table with two new columns: estimatedLevel and pixScore

* [US-574] [TECH] Fixing linting issues

* [US-574] [TECH] Refactoring the way we set pixScore and estimatedLevel in Assessment

* [US-574] [FEATURE] Saving assessment with level and score

* [US-574] [FEATURE] Retrieving linked Competences with a course from Airtable

* WIP

* [US-574] [FEATURE] Add getByUserId in Assessment repository and add competences to course object in serializer

* [US-574] [FEATURE] Assigning level and score for each competence

* [US-574] [FEATURE] Exposing level and score by competence on /api/users

* [US-574] [CLEANUP] Auto code review

* [#574] [TECH] change field pixScore to pix-score in endpoint get /users

* [#574] [CLEANUP] Use sinon.sandbox if too many stubs in test files
  • Loading branch information
Brandone MARTINS committed Jul 7, 2017
1 parent ac672d3 commit 0756037
Show file tree
Hide file tree
Showing 36 changed files with 1,099 additions and 424 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const TABLE_NAME = 'assessments';

exports.up = function(knex, Promise) {
return Promise.all([
knex.schema.table(TABLE_NAME, function (table) {
table.bigInteger('userId').index().references('users.id');
table.dropColumn('userName');
table.dropColumn('userEmail');
})
]);
};

exports.down = function(knex, Promise) {
return Promise.all([
knex.schema.table(TABLE_NAME, function (table) {
table.dropColumn('userId');
table.string('userName').notNull();
table.string('userEmail').notNull();
})
]);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const TABLE_NAME = 'assessments';

exports.up = function(knex, Promise) {
return Promise.all([
knex.schema.table(TABLE_NAME, function (table) {
table.integer('estimatedLevel');
table.integer('pixScore');
})
]);
};

exports.down = function(knex, Promise) {
return Promise.all([
knex.schema.table(TABLE_NAME, function (table) {
table.dropColumn('estimatedLevel');
table.dropColumn('pixScore');
})
]);
};
46 changes: 27 additions & 19 deletions api/db/seeds/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,33 @@ exports.seed = (knex) => {

return knex(TABLE_NAME).del().then(() => {

return knex(TABLE_NAME).insert([{

firstName: 'Jon',
lastName: 'Snow',
email: 'jsnow@winterfell.got',
password: 'WinterIsComing'
}, {

firstName: 'Daenerys',
lastName: 'Targaryen',
email: 'dtargaryen@targaryen.got',
password: 'A1B2C3#!'
}, {

firstName: 'Tyron',
lastName: 'Lannister',
email: 'tlannister@lannister.got',
password: 'P@s$w0rD'
}]);
return knex(TABLE_NAME).insert([
{
firstName: 'Jon',
lastName: 'Snow',
email: 'jsnow@winterfell.got',
password: 'WinterIsComing'
}, {

firstName: 'Daenerys',
lastName: 'Targaryen',
email: 'dtargaryen@targaryen.got',
password: 'A1B2C3#!'
}, {

firstName: 'Tyron',
lastName: 'Lannister',
email: 'tlannister@lannister.got',
password: 'P@s$w0rD'
},
{

firstName: 'Pix',
lastName: 'Aile',
email: 'pix@contact.com',
password: '$2a$05$.EhuqNtCbjSKJlv7X2mO5.xO9hu1DuMvA1JGFLyExzFQ4ywN4oOBC'
}
]);

});

Expand Down
33 changes: 30 additions & 3 deletions api/lib/application/assessments/assessment-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const _ = require('../../infrastructure/utils/lodash-utils');
const assessmentSerializer = require('../../infrastructure/serializers/jsonapi/assessment-serializer');
const assessmentRepository = require('../../infrastructure/repositories/assessment-repository');
const assessmentService = require('../../domain/services/assessment-service');
const tokenService = require('../../domain/services/token-service');
const assessmentUtils = require('../../domain/services/assessment-service-utils');
const challengeRepository = require('../../infrastructure/repositories/challenge-repository');
const challengeSerializer = require('../../infrastructure/serializers/jsonapi/challenge-serializer');
Expand All @@ -13,15 +14,25 @@ const courseRepository = require('../../infrastructure/repositories/course-repos
const answerRepository = require('../../infrastructure/repositories/answer-repository');
const solutionRepository = require('../../infrastructure/repositories/solution-repository');

const { NotFoundError, NotElligibleToScoringError } = require('../../domain/errors');
const {NotFoundError, NotElligibleToScoringError} = require('../../domain/errors');

module.exports = {

save(request, reply) {

const assessment = assessmentSerializer.deserialize(request.payload);

if (request.headers.hasOwnProperty('authorization')) {
const token = tokenService.extractTokenFromAuthChain(request.headers.authorization);
const userId = tokenService.extractUserId(token);

assessment.set('userId', userId);
}

return assessment.save()
.then((assessment) => reply(assessmentSerializer.serialize(assessment)).code(201))
.then(assessment => {
reply(assessmentSerializer.serialize(assessment)).code(201);
})
.catch((err) => reply(Boom.badImplementation(err)));
},

Expand Down Expand Up @@ -54,11 +65,27 @@ module.exports = {

getNextChallenge(request, reply) {

assessmentRepository
return assessmentRepository
.get(request.params.id)
.then((assessment) => {
return assessmentService.getAssessmentNextChallengeId(assessment, request.params.challengeId);
})
.then((nextChallengeId) => {

if (nextChallengeId) {
return Promise.resolve(nextChallengeId);
}

return assessmentService
.getScoredAssessment(request.params.id)
.then((scoredAssessment) => {
return scoredAssessment.save()
.then(() => {
return nextChallengeId;
});
});

})
.then((nextChallengeId) => {
return (nextChallengeId) ? challengeRepository.get(nextChallengeId) : null;
})
Expand Down
14 changes: 3 additions & 11 deletions api/lib/application/authentication/authentication-controller.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
const User = require('../../../lib/domain/models/data/user');

const jsonwebtoken = require('jsonwebtoken');

const encrypt = require('../../domain/services/encryption-service');
const tokenService = require('../../domain/services/token-service');

const validationErrorSerializer = require('../../infrastructure/serializers/jsonapi/validation-error-serializer');
const settings = require('../../settings');

const Authentication = require('../../domain/models/data/authentication');
const authenticationSerializer = require('../../infrastructure/serializers/jsonapi/authentication-serializer');
Expand All @@ -27,7 +26,7 @@ module.exports = {
return encrypt.check(password, foundUser.get('password'));
})
.then(_ => {
const token = _createTokenFromUser(user);
const token = tokenService.createTokenFromUser(user);

const authentication = new Authentication(user.get('id'), token);
return reply(authenticationSerializer.serialize(authentication)).code(201);
Expand All @@ -39,13 +38,6 @@ module.exports = {
}
};

function _createTokenFromUser(user) {
return jsonwebtoken.sign({
user_id: user.get('id'),
email: user.get('email')
}, settings.authentication.secret, { expiresIn: settings.authentication.tokenLifespan });
}

function _extractAttributes(request) {
return request.payload.data.attributes;
}
Expand Down
10 changes: 5 additions & 5 deletions api/lib/application/users/user-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ module.exports = {

save(request, reply) {

if(!_.has(request, 'payload') || !_.has(request, 'payload.data.attributes')) {
if (!_.has(request, 'payload') || !_.has(request, 'payload.data.attributes')) {
return reply(Boom.badRequest());
}

Expand All @@ -42,11 +42,11 @@ module.exports = {
mailService.sendAccountCreationEmail(user.get('email'));
reply(userSerializer.serialize(user)).code(201);
}).catch((err) => {
if(err instanceof InvalidRecaptchaTokenError) {
if (err instanceof InvalidRecaptchaTokenError) {
const userValidationErrors = user.validationErrors();
err = _buildErrorWhenRecaptchaTokenInvalid(userValidationErrors);
}
if(_isUniqConstraintViolated(err)) {
if (_isUniqConstraintViolated(err)) {
err = _buildErrorWhenUniquEmail();
}

Expand All @@ -66,11 +66,11 @@ module.exports = {
reply(profileSerializer.serialize(buildedProfile)).code(201);
})
.catch((err) => {
if(err instanceof InvalidTokenError) {
if (err instanceof InvalidTokenError) {
return _replyErrorWithMessage(reply, 'Le token n’est pas valide', 401);
}

if(err === User.NotFoundError) {
if (err === User.NotFoundError) {
return _replyErrorWithMessage(reply, 'Cet utilisateur est introuvable', 404);
}

Expand Down
30 changes: 26 additions & 4 deletions api/lib/domain/models/data/profile.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,38 @@
const _ = require('lodash');

class Profile {
constructor(user, competences, areas) {
constructor(user, competences, areas, assessments, courses) {
this.user = user;
this.competences = competences;
this.areas = areas;
this.setLevelToCompetences();
this.initCompetenceLevel();
this.setLevelToCompetences(assessments, courses);
}

setLevelToCompetences() {
if(this.competences) {
initCompetenceLevel() {
if (this.competences) {
this.competences.forEach((competence) => competence['level'] = -1);
}
}

setLevelToCompetences(assessments, courses) {
assessments.forEach((assessment) => {
const courseId = assessment.get('courseId');

const course = _.find(courses, function(course) {
return course.id === courseId;
});

course.competences.forEach((competenceId) => {
const linkedCompetence = _.find(this.competences, function(competence) {
return competence.id === competenceId;
});

linkedCompetence.level = assessment.get('estimatedLevel');
linkedCompetence.pixScore = assessment.get('pixScore');
});
});
}
}

module.exports = Profile;
32 changes: 17 additions & 15 deletions api/lib/domain/services/assessment-service.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const challengeRepository = require('../../infrastructure/repositories/challenge
const assessmentUtils = require('./assessment-service-utils');
const _ = require('../../infrastructure/utils/lodash-utils');

const { NotFoundError, NotElligibleToScoringError } = require('../../domain/errors');
const {NotFoundError, NotElligibleToScoringError} = require('../../domain/errors');

const scoringService = require('../../domain/services/scoring-service');

Expand Down Expand Up @@ -37,21 +37,17 @@ function _selectNextInNormalMode(currentChallengeId, challenges) {

}

function _completeAssessmentWithScore(assessment, answers, knowledgeData) {

if (answers.length === 0) {
return assessment;
}
function _getAssessmentResultDetails(answers, knowledgeData) {

const performanceStats = scoringService.getPerformanceStats(answers, knowledgeData);
const diagnosis = scoringService.computeDiagnosis(performanceStats, knowledgeData);

assessment.set('estimatedLevel', diagnosis.estimatedLevel);
assessment.set('pixScore', diagnosis.pixScore);
assessment.set('notAcquiredKnowledgeTags', performanceStats.notAcquiredKnowledgeTags);
assessment.set('acquiredKnowledgeTags', performanceStats.acquiredKnowledgeTags);

return assessment;
return {
'estimatedLevel': diagnosis.estimatedLevel,
'pixScore': diagnosis.pixScore,
'notAcquiredKnowledgeTags': performanceStats.notAcquiredKnowledgeTags,
'acquiredKnowledgeTags': performanceStats.acquiredKnowledgeTags
};
}

function selectNextChallengeId(course, currentChallengeId, assessment) {
Expand Down Expand Up @@ -89,6 +85,7 @@ function getScoredAssessment(assessmentId) {
}

assessment = retrievedAssessment;

return answerRepository.findByAssessment(assessment.get('id'));
})
.then(retrievedAnswers => {
Expand All @@ -100,10 +97,15 @@ function getScoredAssessment(assessmentId) {
return Promise.all(challengePromises);
})
.then(challenges => {

const knowledgeData = challengeService.getKnowledgeData(challenges);

const scoredAssessment = _completeAssessmentWithScore(assessment, answers, knowledgeData);
resolve(scoredAssessment);
const resultDetails = _getAssessmentResultDetails(answers, knowledgeData);

assessment.set('estimatedLevel', resultDetails.estimatedLevel);
assessment.set('pixScore', resultDetails.pixScore);

resolve(assessment);
})
.catch(reject);
});
Expand Down Expand Up @@ -138,6 +140,6 @@ module.exports = {
getAssessmentNextChallengeId,
getScoredAssessment,

_completeAssessmentWithScore
_getAssessmentResultDetails

};
11 changes: 8 additions & 3 deletions api/lib/domain/services/profile-service.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const userRepository = require('../../infrastructure/repositories/user-repository');
const competenceRepository = require('../../infrastructure/repositories/competence-repository');
const areaRepository = require('../../infrastructure/repositories/area-repository');
const courseRepository = require('../../infrastructure/repositories/course-repository');
const assessmentRepository = require('../../infrastructure/repositories/assessment-repository');

const Profile = require('../../domain/models/data/profile');

Expand All @@ -10,9 +12,12 @@ const profileService = {
const competences = competenceRepository.list();
const areas = areaRepository.list();

return Promise.all([user, competences, areas])
.then(([user, competences, areas]) => {
return new Profile(user, competences, areas);
const adaptiveCourses = courseRepository.getAdaptiveCourses();
const assessments = assessmentRepository.getByUserId(user_id);

return Promise.all([user, competences, areas, assessments, adaptiveCourses])
.then(([user, competences, areas, assessments, adaptiveCourses]) => {
return new Profile(user, competences, areas, assessments, adaptiveCourses);
});
}
};
Expand Down
Loading

0 comments on commit 0756037

Please sign in to comment.