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 ); } }