feat(certs): Convert certification views to gatsby
This commit is contained in:
committed by
mrugesh mohapatra
parent
dc00eb8555
commit
77a4452437
@ -1,23 +1,18 @@
|
||||
import _ from 'lodash';
|
||||
import loopback from 'loopback';
|
||||
import moment from 'moment-timezone';
|
||||
import path from 'path';
|
||||
import dedent from 'dedent';
|
||||
import { Observable } from 'rx';
|
||||
import debug from 'debug';
|
||||
import { isEmail } from 'validator';
|
||||
import format from 'date-fns/format';
|
||||
|
||||
import {
|
||||
ifNoUser401
|
||||
} from '../utils/middleware';
|
||||
|
||||
import { ifNoUser401 } from '../utils/middleware';
|
||||
import { observeQuery } from '../utils/rx';
|
||||
|
||||
import {
|
||||
legacyFrontEndChallengeId,
|
||||
legacyBackEndChallengeId,
|
||||
legacyDataVisId,
|
||||
|
||||
respWebDesignId,
|
||||
frontEndLibsId,
|
||||
jsAlgoDataStructId,
|
||||
@ -28,26 +23,83 @@ import {
|
||||
} from '../utils/constantStrings.json';
|
||||
import certTypes from '../utils/certTypes.json';
|
||||
import superBlockCertTypeMap from '../utils/superBlockCertTypeMap';
|
||||
import {
|
||||
completeCommitment$
|
||||
} from '../utils/commit';
|
||||
import { completeCommitment$ } from '../utils/commit';
|
||||
|
||||
const log = debug('fcc:certification');
|
||||
const renderCertifedEmail = loopback.template(path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'views',
|
||||
'emails',
|
||||
'certified.ejs'
|
||||
));
|
||||
|
||||
export default function bootCertificate(app) {
|
||||
const api = app.loopback.Router();
|
||||
|
||||
const certTypeIds = createCertTypeIds(app);
|
||||
const showCert = createShowCert(app);
|
||||
const verifyCert = createVerifyCert(certTypeIds, app);
|
||||
|
||||
api.post('/certificate/verify', ifNoUser401, ifNoSuperBlock404, verifyCert);
|
||||
api.get('/certificate/showCert/:username/:cert', showCert);
|
||||
|
||||
app.use('/internal', api);
|
||||
}
|
||||
|
||||
const noNameMessage = dedent`
|
||||
We need your name so we can put it on your certification.
|
||||
Add your name to your account settings and click the save button.
|
||||
Then we can issue your certification.
|
||||
`;
|
||||
|
||||
const notCertifiedMessage = name => dedent`
|
||||
it looks like you have not completed the necessary 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 successfully claimed
|
||||
the ${name}!
|
||||
Congratulations on behalf of the freeCodeCamp.org team!
|
||||
`;
|
||||
|
||||
function ifNoSuperBlock404(req, res, next) {
|
||||
const { superBlock } = req.body;
|
||||
if (superBlock && superBlocks.includes(superBlock)) {
|
||||
return next();
|
||||
}
|
||||
return res.status(404).end();
|
||||
}
|
||||
|
||||
const renderCertifedEmail = loopback.template(
|
||||
path.join(__dirname, '..', 'views', 'emails', 'certified.ejs')
|
||||
);
|
||||
|
||||
function createCertTypeIds(app) {
|
||||
const { Challenge } = app.models;
|
||||
|
||||
return {
|
||||
// legacy
|
||||
[certTypes.frontEnd]: getIdsForCert$(legacyFrontEndChallengeId, Challenge),
|
||||
[certTypes.backEnd]: getIdsForCert$(legacyBackEndChallengeId, Challenge),
|
||||
[certTypes.dataVis]: getIdsForCert$(legacyDataVisId, 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),
|
||||
[certTypes.fullStack]: getIdsForCert$(fullStackId, Challenge)
|
||||
};
|
||||
}
|
||||
|
||||
function isCertified(ids, completedChallenges = []) {
|
||||
return _.every(
|
||||
ids,
|
||||
({ id }) => _.find(
|
||||
completedChallenges,
|
||||
({ id: completedId }) => completedId === id
|
||||
)
|
||||
return _.every(ids, ({ id }) =>
|
||||
_.find(completedChallenges, ({ id: completedId }) => completedId === id)
|
||||
);
|
||||
}
|
||||
|
||||
@ -64,51 +116,43 @@ const certIds = {
|
||||
[certTypes.fullStack]: fullStackId
|
||||
};
|
||||
|
||||
const certViews = {
|
||||
[certTypes.frontEnd]: 'certificate/legacy/front-end.jade',
|
||||
[certTypes.backEnd]: 'certificate/legacy/back-end.jade',
|
||||
[certTypes.dataVis]: 'certificate/legacy/data-visualization.jade',
|
||||
|
||||
[certTypes.respWebDesign]: 'certificate/responsive-web-design.jade',
|
||||
[certTypes.frontEndLibs]: 'certificate/front-end-libraries.jade',
|
||||
[certTypes.jsAlgoDataStruct]:
|
||||
'certificate/javascript-algorithms-and-data-structures.jade',
|
||||
[certTypes.dataVis2018]: 'certificate/data-visualization.jade',
|
||||
[certTypes.apisMicroservices]: 'certificate/apis-and-microservices.jade',
|
||||
[certTypes.infosecQa]:
|
||||
'certificate/information-security-and-quality-assurance.jade',
|
||||
[certTypes.fullStack]: 'certificate/full-stack.jade'
|
||||
};
|
||||
|
||||
const certText = {
|
||||
[certTypes.frontEnd]: 'Legacy Front End',
|
||||
[certTypes.backEnd]: 'Legacy Back End',
|
||||
[certTypes.dataVis]: 'Legacy Data Visualization',
|
||||
[certTypes.fullStack]: 'Legacy Full Stack',
|
||||
[certTypes.fullStack]: 'Full Stack',
|
||||
[certTypes.respWebDesign]: 'Responsive Web Design',
|
||||
[certTypes.frontEndLibs]: 'Front End Libraries',
|
||||
[certTypes.jsAlgoDataStruct]:
|
||||
'JavaScript Algorithms and Data Structures',
|
||||
[certTypes.jsAlgoDataStruct]: 'JavaScript Algorithms and Data Structures',
|
||||
[certTypes.dataVis2018]: 'Data Visualization',
|
||||
[certTypes.apisMicroservices]: 'APIs and Microservices',
|
||||
[certTypes.infosecQa]: 'Information Security and Quality Assurance'
|
||||
};
|
||||
|
||||
const completionHours = {
|
||||
[certTypes.frontEnd]: 400,
|
||||
[certTypes.backEnd]: 400,
|
||||
[certTypes.dataVis]: 400,
|
||||
[certTypes.fullStack]: 1800,
|
||||
[certTypes.respWebDesign]: 300,
|
||||
[certTypes.frontEndLibs]: 300,
|
||||
[certTypes.jsAlgoDataStruct]: 300,
|
||||
[certTypes.dataVis2018]: 300,
|
||||
[certTypes.apisMicroservices]: 300,
|
||||
[certTypes.infosecQa]: 300
|
||||
};
|
||||
|
||||
function getIdsForCert$(id, Challenge) {
|
||||
return observeQuery(
|
||||
Challenge,
|
||||
'findById',
|
||||
id,
|
||||
{
|
||||
id: true,
|
||||
tests: true,
|
||||
name: true,
|
||||
challengeType: true
|
||||
}
|
||||
)
|
||||
.shareReplay();
|
||||
return observeQuery(Challenge, 'findById', id, {
|
||||
id: true,
|
||||
tests: true,
|
||||
name: true,
|
||||
challengeType: true
|
||||
}).shareReplay();
|
||||
}
|
||||
|
||||
const superBlocks = Object.keys(superBlockCertTypeMap);
|
||||
|
||||
function sendCertifiedEmail(
|
||||
{
|
||||
email = '',
|
||||
@ -150,111 +194,18 @@ function sendCertifiedEmail(
|
||||
return send$(notifyUser).map(() => true);
|
||||
}
|
||||
|
||||
export default function certificate(app) {
|
||||
const router = app.loopback.Router();
|
||||
const { Email, Challenge, User } = app.models;
|
||||
|
||||
function findUserByUsername$(username, fields) {
|
||||
return observeQuery(
|
||||
User,
|
||||
'findOne',
|
||||
{
|
||||
where: { username },
|
||||
fields
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const certTypeIds = {
|
||||
// legacy
|
||||
[certTypes.frontEnd]: getIdsForCert$(legacyFrontEndChallengeId, Challenge),
|
||||
[certTypes.backEnd]: getIdsForCert$(legacyBackEndChallengeId, Challenge),
|
||||
[certTypes.dataVis]: getIdsForCert$(legacyDataVisId, 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),
|
||||
[certTypes.fullStack]: getIdsForCert$(fullStackId, Challenge)
|
||||
};
|
||||
|
||||
const superBlocks = Object.keys(superBlockCertTypeMap);
|
||||
|
||||
router.get(
|
||||
'/:username/front-end-certification',
|
||||
(req, res) => res.redirect(
|
||||
`/certification/${req.params.username}/legacy-front-end`
|
||||
)
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:username/data-visualization-certification',
|
||||
(req, res) => res.redirect(
|
||||
`/certification/${req.params.username}/legacy-data-visualization`
|
||||
)
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:username/back-end-certification',
|
||||
(req, res) => res.redirect(
|
||||
`/certification/${req.params.username}/legacy-back-end`
|
||||
)
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/:username/full-stack-certification',
|
||||
(req, res) => res.redirect(
|
||||
`/certification/${req.params.username}/full-stack`
|
||||
)
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/certificate/verify',
|
||||
ifNoUser401,
|
||||
ifNoSuperBlock404,
|
||||
verifyCert
|
||||
);
|
||||
router.get(
|
||||
'/certification/:username/:cert',
|
||||
showCert
|
||||
);
|
||||
|
||||
app.use(router);
|
||||
|
||||
const noNameMessage = dedent`
|
||||
We need your name so we can put it on your certification.
|
||||
Add your name to your account settings and click the save button.
|
||||
Then we can issue your certification.
|
||||
`;
|
||||
|
||||
const notCertifiedMessage = name => dedent`
|
||||
it looks like you have not completed the necessary 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 successfully claimed
|
||||
the ${name}!
|
||||
Congratulations on behalf of the freeCodeCamp team!
|
||||
`;
|
||||
|
||||
function verifyCert(req, res, next) {
|
||||
const { body: { superBlock }, user } = req;
|
||||
function createVerifyCert(certTypeIds, app) {
|
||||
const { Email } = app.models;
|
||||
return function verifyCert(req, res, next) {
|
||||
const {
|
||||
body: { superBlock },
|
||||
user
|
||||
} = req;
|
||||
log(superBlock);
|
||||
let certType = superBlockCertTypeMap[superBlock];
|
||||
log(certType);
|
||||
return user.getCompletedChallenges$()
|
||||
return user
|
||||
.getCompletedChallenges$()
|
||||
.flatMap(() => certTypeIds[certType])
|
||||
.flatMap(challenge => {
|
||||
const certName = certText[certType];
|
||||
@ -263,31 +214,28 @@ export default function certificate(app) {
|
||||
}
|
||||
|
||||
let updateData = {
|
||||
$set: {
|
||||
[certType]: true
|
||||
}
|
||||
[certType]: true
|
||||
};
|
||||
|
||||
if (challenge) {
|
||||
const {
|
||||
id,
|
||||
tests,
|
||||
challengeType
|
||||
} = challenge;
|
||||
if (!user[certType] &&
|
||||
!isCertified(tests, user.completedChallenges)) {
|
||||
const { id, tests, challengeType } = challenge;
|
||||
if (
|
||||
!user[certType] &&
|
||||
!isCertified(tests, user.completedChallenges)
|
||||
) {
|
||||
return Observable.just(notCertifiedMessage(certName));
|
||||
}
|
||||
updateData['$push'] = {
|
||||
completedChallenges: {
|
||||
id,
|
||||
completedDate: new Date(),
|
||||
challengeType
|
||||
}
|
||||
updateData = {
|
||||
...updateData,
|
||||
completedChallenges: [
|
||||
...user.completedChallenges,
|
||||
{
|
||||
id,
|
||||
completedDate: new Date(),
|
||||
challengeType
|
||||
}
|
||||
]
|
||||
};
|
||||
user.completedChallenges[
|
||||
user.completedChallenges.length - 1
|
||||
] = { id, completedDate: new Date() };
|
||||
}
|
||||
|
||||
if (!user.name) {
|
||||
@ -306,145 +254,164 @@ export default function certificate(app) {
|
||||
// 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, certName);
|
||||
}
|
||||
);
|
||||
})
|
||||
.subscribe(
|
||||
(message) => {
|
||||
return res.status(200).json({
|
||||
message,
|
||||
success: message.includes('Congratulations')
|
||||
});
|
||||
},
|
||||
next
|
||||
);
|
||||
).map(({ count, pledgeOrMessage }) => {
|
||||
if (typeof pledgeOrMessage === 'string') {
|
||||
log(pledgeOrMessage);
|
||||
}
|
||||
log(`${count} documents updated`);
|
||||
return successMessage(user.username, certName);
|
||||
});
|
||||
})
|
||||
.subscribe(message => {
|
||||
return res.status(200).json({
|
||||
message,
|
||||
success: message.includes('Congratulations')
|
||||
});
|
||||
}, next);
|
||||
};
|
||||
}
|
||||
|
||||
function createShowCert(app) {
|
||||
const { User } = app.models;
|
||||
|
||||
function findUserByUsername$(username, fields) {
|
||||
return observeQuery(User, 'findOne', {
|
||||
where: { username },
|
||||
fields
|
||||
});
|
||||
}
|
||||
|
||||
function ifNoSuperBlock404(req, res, next) {
|
||||
const { superBlock } = req.body;
|
||||
if (superBlock && superBlocks.includes(superBlock)) {
|
||||
return next();
|
||||
}
|
||||
return res.status(404).end();
|
||||
}
|
||||
|
||||
function showCert(req, res, next) {
|
||||
return function showCert(req, res, next) {
|
||||
let { username, cert } = req.params;
|
||||
username = username.toLowerCase();
|
||||
const certType = superBlockCertTypeMap[cert];
|
||||
const certId = certIds[certType];
|
||||
return findUserByUsername$(
|
||||
username,
|
||||
{
|
||||
isCheater: true,
|
||||
isFrontEndCert: true,
|
||||
isBackEndCert: true,
|
||||
isFullStackCert: true,
|
||||
isRespWebDesignCert: true,
|
||||
isFrontEndLibsCert: true,
|
||||
isJsAlgoDataStructCert: true,
|
||||
isDataVisCert: true,
|
||||
is2018DataVisCert: true,
|
||||
isApisMicroservicesCert: true,
|
||||
isInfosecQaCert: true,
|
||||
isHonest: true,
|
||||
username: true,
|
||||
name: true,
|
||||
completedChallenges: true,
|
||||
profileUI: true
|
||||
const certTitle = certText[certType];
|
||||
const completionTime = completionHours[certType] || 300;
|
||||
return findUserByUsername$(username, {
|
||||
isCheater: true,
|
||||
isFrontEndCert: true,
|
||||
isBackEndCert: true,
|
||||
isFullStackCert: true,
|
||||
isRespWebDesignCert: true,
|
||||
isFrontEndLibsCert: true,
|
||||
isJsAlgoDataStructCert: true,
|
||||
isDataVisCert: true,
|
||||
is2018DataVisCert: true,
|
||||
isApisMicroservicesCert: true,
|
||||
isInfosecQaCert: true,
|
||||
isHonest: true,
|
||||
username: true,
|
||||
name: true,
|
||||
completedChallenges: true,
|
||||
profileUI: true
|
||||
}).subscribe(user => {
|
||||
if (!user) {
|
||||
return res.json({
|
||||
messages: [
|
||||
{
|
||||
type: 'info',
|
||||
message: `We could not find a user with the username "${username}"`
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
)
|
||||
.subscribe(
|
||||
user => {
|
||||
if (!user) {
|
||||
req.flash(
|
||||
'danger',
|
||||
`We couldn't find a user with the username ${username}`
|
||||
);
|
||||
return res.redirect('/');
|
||||
}
|
||||
const { isLocked, showCerts } = user.profileUI;
|
||||
const profile = `/portfolio/${user.username}`;
|
||||
const { isLocked, showCerts } = user.profileUI;
|
||||
|
||||
if (!user.name) {
|
||||
req.flash(
|
||||
'danger',
|
||||
dedent`
|
||||
if (!user.name) {
|
||||
return res.json({
|
||||
messages: [
|
||||
{
|
||||
type: 'info',
|
||||
message: dedent`
|
||||
This user needs to add their name to their account
|
||||
in order for others to be able to view their certification.
|
||||
`
|
||||
);
|
||||
return res.redirect(profile);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (user.isCheater) {
|
||||
return res.redirect(profile);
|
||||
}
|
||||
if (user.isCheater) {
|
||||
return res.json({
|
||||
messages: [
|
||||
{
|
||||
type: 'info',
|
||||
message:
|
||||
'This user is not eligible for freeCodeCamp.org ' +
|
||||
'certifications at this time'
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (isLocked) {
|
||||
req.flash(
|
||||
'danger',
|
||||
dedent`
|
||||
if (isLocked) {
|
||||
return res.json({
|
||||
messages: [
|
||||
{
|
||||
type: 'info',
|
||||
message: dedent`
|
||||
${username} has chosen to make their profile
|
||||
private. They will need to make their profile public
|
||||
in order for others to be able to view their certification.
|
||||
`
|
||||
);
|
||||
return res.redirect('/');
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (!showCerts) {
|
||||
req.flash(
|
||||
'danger',
|
||||
dedent`
|
||||
if (!showCerts) {
|
||||
return res.json({
|
||||
messages: [
|
||||
{
|
||||
type: 'info',
|
||||
message: dedent`
|
||||
${username} has chosen to make their certifications
|
||||
private. They will need to make their certifications public
|
||||
in order for others to be able to view them.
|
||||
`
|
||||
);
|
||||
return res.redirect('/');
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (!user.isHonest) {
|
||||
req.flash(
|
||||
'danger',
|
||||
dedent`
|
||||
if (!user.isHonest) {
|
||||
return res.json({
|
||||
messages: [
|
||||
{
|
||||
type: 'info',
|
||||
message: dedent`
|
||||
${username} has not yet agreed to our Academic Honesty Pledge.
|
||||
`
|
||||
);
|
||||
return res.redirect(profile);
|
||||
}
|
||||
|
||||
if (user[certType]) {
|
||||
const { completedChallenges = [] } = user;
|
||||
const { completedDate = new Date() } = _.find(
|
||||
completedChallenges, ({ id }) => certId === id
|
||||
) || {};
|
||||
|
||||
return res.render(
|
||||
certViews[certType],
|
||||
{
|
||||
username: user.username,
|
||||
date: moment(new Date(completedDate)).format('MMMM D, YYYY'),
|
||||
name: user.name
|
||||
}
|
||||
);
|
||||
}
|
||||
req.flash(
|
||||
'danger',
|
||||
`Looks like user ${username} is not ${certText[certType]} certified`
|
||||
);
|
||||
return res.redirect(profile);
|
||||
},
|
||||
next
|
||||
);
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (user[certType]) {
|
||||
const { completedChallenges = [] } = user;
|
||||
const { completedDate = new Date() } =
|
||||
_.find(completedChallenges, ({ id }) => certId === id) || {};
|
||||
|
||||
const { username, name } = user;
|
||||
return res.json({
|
||||
certTitle,
|
||||
username,
|
||||
name,
|
||||
date: format(new Date(completedDate), 'MMMM D, YYYY'),
|
||||
completionTime
|
||||
});
|
||||
}
|
||||
return res.json({
|
||||
messages: [
|
||||
{
|
||||
type: 'info',
|
||||
message: `It looks like user ${username} is not ${
|
||||
certText[certType]
|
||||
} certified`
|
||||
}
|
||||
]
|
||||
});
|
||||
}, next);
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user