import _ from 'lodash';
import moment from 'moment';
import loopback from 'loopback';
import path from 'path';
import dedent from 'dedent';
import { Observable } from 'rx';
import debug from 'debug';
import {
ifNoUser401,
ifNoUserSend
} from '../utils/middleware';
import { observeQuery } from '../utils/rx';
import {
frontEndChallengeId,
dataVisChallengeId,
backEndChallengeId
} 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 renderNotifyEmail = loopback.template(path.join(
__dirname,
'..',
'views',
'emails',
'a-new-user.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();
}
// getFormatedDate(challengeMap: Object, challengeId: String) => String, throws
function getFormatedDate(challengeMap, challengeId) {
return moment(challengeMap[challengeId].completedDate)
.format('MMMM D, YYYY');
}
// sendCertifiedEmail(
// {
// email: String,
// username: String,
// isFrontEndCert: Boolean,
// isBackEndCert: Boolean,
// isDataVisCert: Boolean
// },
// send$: Observable
// ) => Observable
function sendCertifiedEmail(
{
email,
name,
username,
isFrontEndCert,
isBackEndCert,
isDataVisCert,
challengeMap
},
send$
) {
if (
!isFrontEndCert ||
!isBackEndCert ||
!isDataVisCert
) {
return Observable.just(false);
}
let frontEndDate;
let backEndDate;
let dataVisDate;
try {
frontEndDate = getFormatedDate(challengeMap, frontEndChallengeId);
backEndDate = getFormatedDate(challengeMap, backEndChallengeId);
dataVisDate = getFormatedDate(challengeMap, dataVisChallengeId);
} catch (err) {
return Observable.throw(err);
}
const notifyTeam = {
type: 'email',
to: 'Michael@FreeCodeCamp.com',
from: 'Team@FreeCodeCamp.com',
subject: 'A new user has arrived!',
text: renderNotifyEmail({
username,
name,
frontEndDate,
dataVisDate,
backEndDate
})
};
const notifyUser = {
type: 'email',
to: email,
from: 'Michael@FreeCodeCamp.com',
subject: 'Congratulation on gaining your third certification!',
text: renderCertifedEmail({
username,
name
})
};
return Observable.combineLatest(
send$(notifyTeam),
send$(notifyUser),
() => true
);
}
export default function certificate(app) {
const router = app.loopback.Router();
const { Email, Challenge } = app.models;
const certTypeIds = {
[certTypes.frontEnd]: getIdsForCert$(frontEndChallengeId, Challenge),
[certTypes.dataVis]: getIdsForCert$(dataVisChallengeId, Challenge),
[certTypes.backEnd]: getIdsForCert$(backEndChallengeId, 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/data-visualization',
ifNoUser401,
verifyCert.bind(null, certTypes.dataVis)
);
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
);
}
}