Files
freeCodeCamp/server/boot/certificate.js

255 lines
6.4 KiB
JavaScript
Raw Normal View History

2015-10-02 11:47:36 -07:00
import _ from 'lodash';
import loopback from 'loopback';
import path from 'path';
2015-10-02 11:47:36 -07:00
import dedent from 'dedent';
import { Observable } from 'rx';
2016-02-09 20:54:49 -08:00
import debug from 'debug';
2017-08-26 00:27:05 +02:00
import { isEmail } from 'validator';
2015-10-02 11:47:36 -07:00
import {
ifNoUser401
2015-10-02 11:47:36 -07:00
} from '../utils/middleware';
2016-02-09 20:54:49 -08:00
import { observeQuery } from '../utils/rx';
2015-10-02 11:47:36 -07:00
import {
// legacy
frontEndChallengeId,
backEndChallengeId,
dataVisId,
// modern
respWebDesignId,
frontEndLibsId,
dataVis2018Id,
jsAlgoDataStructId,
apisMicroservicesId,
infosecQaId
} from '../utils/constantStrings.json';
import {
completeCommitment$
} from '../utils/commit';
2016-01-11 15:58:37 -08:00
import certTypes from '../utils/certTypes.json';
import superBlockCertTypeMap from '../utils/superBlockCertTypeMap';
2016-01-11 15:58:37 -08:00
2016-01-27 11:34:44 -08:00
const log = debug('fcc:certification');
const renderCertifedEmail = loopback.template(path.join(
__dirname,
'..',
'views',
'emails',
'certified.ejs'
));
2015-10-02 11:47:36 -07:00
2016-02-09 20:54:49 -08:00
function isCertified(ids, challengeMap = {}) {
return _.every(ids, ({ id }) => _.has(challengeMap, id));
2015-10-02 11:47:36 -07:00
}
2016-01-11 15:58:37 -08:00
function getIdsForCert$(id, Challenge) {
return observeQuery(
2015-10-02 11:47:36 -07:00
Challenge,
'findById',
2016-01-11 15:58:37 -08:00
id,
2015-10-02 11:47:36 -07:00
{
id: true,
tests: true,
name: true,
challengeType: true
2015-10-02 11:47:36 -07:00
}
)
.shareReplay();
2016-01-11 15:58:37 -08:00
}
2015-10-02 11:47:36 -07:00
// sendCertifiedEmail(
// {
// email: String,
// username: String,
// isRespWebDesignCert: Boolean,
// isFrontEndLibsCert: Boolean,
// isJsAlgoDataStructCert: Boolean,
// isDataVisCert: Boolean,
// isApisMicroservicesCert: Boolean,
// isInfosecQaCert: Boolean
// },
// send$: Observable
// ) => Observable
function sendCertifiedEmail(
{
email,
name,
username,
isRespWebDesignCert,
isFrontEndLibsCert,
isJsAlgoDataStructCert,
isDataVisCert,
isApisMicroservicesCert,
isInfosecQaCert
},
send$
) {
if (
2017-08-26 00:27:05 +02:00
!isEmail(email) ||
!isRespWebDesignCert ||
!isFrontEndLibsCert ||
!isJsAlgoDataStructCert ||
!isDataVisCert ||
!isApisMicroservicesCert ||
!isInfosecQaCert
) {
return Observable.just(false);
}
const notifyUser = {
type: 'email',
to: email,
from: 'team@freeCodeCamp.org',
subject: dedent`
Congratulations on completing all of the
freeCodeCamp certificates!
`,
text: renderCertifedEmail({
username,
name
})
};
2017-08-26 00:27:05 +02:00
return send$(notifyUser).map(() => true);
}
2016-01-11 15:58:37 -08:00
export default function certificate(app) {
const router = app.loopback.Router();
const { Email, Challenge } = app.models;
2016-01-11 15:58:37 -08:00
const certTypeIds = {
// legacy
2016-01-11 15:58:37 -08:00
[certTypes.frontEnd]: getIdsForCert$(frontEndChallengeId, Challenge),
[certTypes.backEnd]: getIdsForCert$(backEndChallengeId, Challenge),
[certTypes.dataVis]: getIdsForCert$(dataVisId, Challenge),
// modern
[certTypes.respWebDesign]: getIdsForCert$(respWebDesignId, Challenge),
[certTypes.frontEndLibs]: getIdsForCert$(frontEndLibsId, Challenge),
[certTypes.dataVis2018]: getIdsForCert$(dataVis2018Id, Challenge),
[certTypes.jsAlgoDataStruct]: getIdsForCert$(jsAlgoDataStructId, Challenge),
[certTypes.apisMicroservices]: getIdsForCert$(
apisMicroservicesId,
Challenge
),
[certTypes.infosecQa]: getIdsForCert$(infosecQaId, Challenge)
2016-01-11 15:58:37 -08:00
};
2015-10-02 11:47:36 -07:00
const superBlocks = Object.keys(superBlockCertTypeMap);
router.post(
'/certificate/verify',
ifNoUser401,
ifNoSuperBlock404,
verifyCert
2015-10-02 11:47:36 -07:00
);
app.use(router);
const noNameMessage = dedent`
We need your name so we can put it on your certificate.
Add your name to your account settings and click the save button.
Then we can issue your certificate.
`;
const notCertifiedMessage = name => dedent`
it looks like you have not completed the neccessary steps.
Please complete the required challenges to claim the
${name}
`;
const alreadyClaimedMessage = name => dedent`
It looks like you already have claimed the ${name}
`;
const successMessage = (username, name) => dedent`
@${username}, you have sucessfully claimed
the ${name}!
Congratulations on behalf of the freeCodeCamp team!
`;
function verifyCert(req, res, next) {
const { body: { superBlock }, user } = req;
let certType = superBlockCertTypeMap[superBlock];
log(certType);
if (certType === 'isDataVisCert') {
certType = 'is2018DataVisCert';
log(certType);
}
2016-04-08 14:24:21 -07:00
return user.getChallengeMap$()
.flatMap(() => certTypeIds[certType])
.flatMap(challenge => {
const {
id,
tests,
name,
challengeType
} = challenge;
if (user[certType]) {
return Observable.just(alreadyClaimedMessage(name));
}
if (!user[certType] && !isCertified(tests, user.challengeMap)) {
return Observable.just(notCertifiedMessage(name));
}
if (!user.name) {
return Observable.just(noNameMessage);
2015-10-02 11:47:36 -07:00
}
const updateData = {
$set: {
[`challengeMap.${id}`]: {
id,
name,
completedDate: new Date(),
challengeType
},
[certType]: true
}
};
// set here so sendCertifiedEmail works properly
// not used otherwise
user[certType] = true;
user.challengeMap[id] = { completedDate: new Date() };
return Observable.combineLatest(
// update user data
user.update$(updateData),
// If user has committed to nonprofit,
// this will complete their pledge
completeCommitment$(user),
// sends notification email is user has all three certs
// if not it noop
sendCertifiedEmail(user, Email.send$),
({ count }, pledgeOrMessage) => ({ count, pledgeOrMessage })
)
.map(
({ count, pledgeOrMessage }) => {
if (typeof pledgeOrMessage === 'string') {
log(pledgeOrMessage);
}
log(`${count} documents updated`);
return successMessage(user.username, name);
}
);
})
2015-10-02 11:47:36 -07:00
.subscribe(
(message) => {
return res.status(200).json({
message,
success: message.includes('Congratulations')
});
2015-10-02 11:47:36 -07:00
},
next
);
}
function ifNoSuperBlock404(req, res, next) {
const { superBlock } = req.body;
if (superBlock && superBlocks.includes(superBlock)) {
return next();
}
return res.status(404).end();
2015-10-02 11:47:36 -07:00
}
}