import _ from 'lodash';
import loopback from 'loopback';
import path from 'path';
import dedent from 'dedent';
import { Observable } from 'rx';
import debug from 'debug';
import { isEmail } from 'validator';
import {
ifNoUser401,
ifNoUserSend
} from '../utils/middleware';
import { observeQuery } from '../utils/rx';
import {
respWebDesignId,
frontEndLibsId,
jsAlgoDataStructId,
frontEndChallengeId,
dataVisId,
apisMicroservicesId,
backEndChallengeId,
infosecQaId
} from '../utils/constantStrings.json';
import {
completeCommitment$
} from '../utils/commit';
import certTypes from '../utils/certTypes.json';
const log = debug('fcc:certification');
const renderCertifedEmail = loopback.template(path.join(
__dirname,
'..',
'views',
'emails',
'certified.ejs'
));
const sendMessageToNonUser = ifNoUserSend(
'must be logged in to complete.'
);
function isCertified(ids, challengeMap = {}) {
return _.every(ids, ({ id }) => challengeMap[id]);
}
function getIdsForCert$(id, Challenge) {
return observeQuery(
Challenge,
'findById',
id,
{
id: true,
tests: true,
name: true,
challengeType: true
}
)
.shareReplay();
}
// 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 (
!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
})
};
return send$(notifyUser).map(() => true);
}
export default function certificate(app) {
const router = app.loopback.Router();
const { Email, Challenge } = app.models;
const certTypeIds = {
[certTypes.frontEnd]: getIdsForCert$(frontEndChallengeId, Challenge),
[certTypes.backEnd]: getIdsForCert$(backEndChallengeId, Challenge),
[certTypes.respWebDesign]: getIdsForCert$(respWebDesignId, Challenge),
[certTypes.frontEndLibs]: getIdsForCert$(frontEndLibsId, Challenge),
[certTypes.jsAlgoDataStruct]: getIdsForCert$(jsAlgoDataStructId, Challenge),
[certTypes.dataVis]: getIdsForCert$(dataVisId, Challenge),
[certTypes.apisMicroservices]: getIdsForCert$(
apisMicroservicesId,
Challenge
),
[certTypes.infosecQa]: getIdsForCert$(infosecQaId, Challenge)
};
router.post(
'/certificate/verify/front-end',
ifNoUser401,
verifyCert.bind(null, certTypes.frontEnd)
);
router.post(
'/certificate/verify/back-end',
ifNoUser401,
verifyCert.bind(null, certTypes.backEnd)
);
router.post(
'/certificate/verify/responsive-web-design',
ifNoUser401,
verifyCert.bind(null, certTypes.respWebDesign)
);
router.post(
'/certificate/verify/front-end-libraries',
ifNoUser401,
verifyCert.bind(null, certTypes.frontEndLibs)
);
router.post(
'/certificate/verify/javascript-algorithms-data-structures',
ifNoUser401,
verifyCert.bind(null, certTypes.jsAlgoDataStruct)
);
router.post(
'/certificate/verify/data-visualization',
ifNoUser401,
verifyCert.bind(null, certTypes.dataVis)
);
router.post(
'/certificate/verify/apis-microservices',
ifNoUser401,
verifyCert.bind(null, certTypes.apisMicroservices)
);
router.post(
'/certificate/verify/information-security-quality-assurance',
ifNoUser401,
verifyCert.bind(null, certTypes.infosecQa)
);
router.post(
'/certificate/honest',
sendMessageToNonUser,
postHonest
);
app.use(router);
function verifyCert(certType, req, res, next) {
const { user } = req;
return user.getChallengeMap$()
.flatMap(() => certTypeIds[certType])
.flatMap(challenge => {
const {
id,
tests,
name,
challengeType
} = challenge;
if (
user[certType] ||
!isCertified(tests, user.challengeMap)
) {
return Observable.just(false);
}
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 true;
}
);
})
.subscribe(
(didCertify) => {
if (didCertify) {
// Check if they have a name set
if (user.name === '') {
return res.status(200).send(
dedent`
We need your name so we can put it on your certificate.
Add your
name to your GitHub account, then go to your
settings
page and click the "update my portfolio from GitHub"
button. Then we can issue your certificate.
`
);
}
return res.status(200).send(true);
}
return res.status(200).send(
dedent`
Looks like you have not completed the neccessary steps.
Please return to the challenge map.
`
);
},
next
);
}
function postHonest(req, res, next) {
return req.user.update$({ $set: { isHonest: true } }).subscribe(
() => res.status(200).send(true),
next
);
}
}