Merge pull request #3570 from FreeCodeCamp/feature/certificates
Add certification page
This commit is contained in:
@ -865,16 +865,55 @@ common.init.push((function() {
|
|||||||
}
|
}
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleActionClick() {
|
function handleActionClick(e) {
|
||||||
$(this)
|
var props = common.challengeSeed[0] ||
|
||||||
|
{ stepIndex: [] };
|
||||||
|
|
||||||
|
var $el = $(this);
|
||||||
|
var index = +$el.attr('id');
|
||||||
|
var propIndex = props.stepIndex.indexOf(index);
|
||||||
|
|
||||||
|
if (propIndex === -1) {
|
||||||
|
return $el
|
||||||
.parent()
|
.parent()
|
||||||
.find('.disabled')
|
.find('.disabled')
|
||||||
.removeClass('disabled');
|
.removeClass('disabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// an API action
|
||||||
|
// prevent link from opening
|
||||||
|
e.preventDefault();
|
||||||
|
var prop = props.properties[propIndex];
|
||||||
|
var api = props.apis[propIndex];
|
||||||
|
if (common[prop]) {
|
||||||
|
return $el
|
||||||
|
.parent()
|
||||||
|
.find('.disabled')
|
||||||
|
.removeClass('disabled');
|
||||||
|
}
|
||||||
|
$
|
||||||
|
.post(api)
|
||||||
|
.done(function(data) {
|
||||||
|
// assume a boolean indicates passing
|
||||||
|
if (typeof data === 'boolean') {
|
||||||
|
return $el
|
||||||
|
.parent()
|
||||||
|
.find('.disabled')
|
||||||
|
.removeClass('disabled');
|
||||||
|
}
|
||||||
|
// assume api returns string when fails
|
||||||
|
$el
|
||||||
|
.parent()
|
||||||
|
.find('.disabled')
|
||||||
|
.replaceWith('<p>' + data + '</p>');
|
||||||
|
})
|
||||||
|
.fail(function() {
|
||||||
|
console.log('failed');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function handleFinishClick(e) {
|
function handleFinishClick(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$(submitModalId).modal('show');
|
$(submitModalId).modal('show');
|
||||||
|
@ -102,14 +102,31 @@
|
|||||||
},
|
},
|
||||||
"isLocked": {
|
"isLocked": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false
|
"default": false,
|
||||||
|
"description": "Campers profile does not show challenges to the public"
|
||||||
},
|
},
|
||||||
"currentChallenge": {
|
"currentChallenge": {
|
||||||
"type": {}
|
"type": {}
|
||||||
},
|
},
|
||||||
"isUniqMigrated": {
|
"isUniqMigrated": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"default": false
|
"default": false,
|
||||||
|
"description": "Campers completedChallenges array is free of duplicates"
|
||||||
|
},
|
||||||
|
"isHonest": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Camper has signed academic honesty policy"
|
||||||
|
},
|
||||||
|
"isFrontEndCert": {
|
||||||
|
"type": "boolean",
|
||||||
|
"defaut": false,
|
||||||
|
"description": "Camper is front end certified"
|
||||||
|
},
|
||||||
|
"isFullStackCert": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Campers is full stack certified"
|
||||||
},
|
},
|
||||||
"completedChallenges": {
|
"completedChallenges": {
|
||||||
"type": [
|
"type": [
|
||||||
|
BIN
public/fonts/saxmono.ttf
Executable file
BIN
public/fonts/saxmono.ttf
Executable file
Binary file not shown.
@ -5,13 +5,36 @@
|
|||||||
{
|
{
|
||||||
"id": "561add10cb82ac38a17513be",
|
"id": "561add10cb82ac38a17513be",
|
||||||
"title": "Claim Your Front End Development Certificate",
|
"title": "Claim Your Front End Development Certificate",
|
||||||
"difficulty": 0.00,
|
"challengeSeed": [
|
||||||
"challengeSeed": [],
|
{
|
||||||
|
"properties": ["isHonest", "isFrontEndCert"],
|
||||||
|
"apis": ["/certificate/honest", "/certificate/verify/front-end"],
|
||||||
|
"stepIndex": [1, 2]
|
||||||
|
}
|
||||||
|
],
|
||||||
"description": [
|
"description": [
|
||||||
[
|
[
|
||||||
"http://i.imgur.com/RlEk2IF.jpg",
|
"http://i.imgur.com/luMkKst.jpg",
|
||||||
"a picture of Free Code Camp's 4 benefits: Get connected, Learn JavaScript, Build your Portfolio, Help nonprofits",
|
"An image of our Front End Development Certificate",
|
||||||
"Welcome to Free Code Camp. We're an open source community of busy people who learn to code and help nonprofits.",
|
"This challenge will give you your verified Front End Development Certificate. Before we issue your certificate, we must verify that you have completed all of our basic and intermediate Bonfires, and all our basic and intermediate Ziplines. You must also accept our Academic Honesty Pledge. Click the button below to start this process.",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"http://i.imgur.com/HArFfMN.jpg",
|
||||||
|
"The definition of plagiarism: Plagiarism (noun) - copying someone else’s work and presenting it as your own without crediting them",
|
||||||
|
"By clicking below, you pledge that all of your submitted code A) is code you or your pair personally wrote, or B) comes from open source libraries like jQuery, or C) has been clearly attributed to its original authors. You also give us permission to audit your challenge solutions and revoke your certificate if we discover evidence of plagiarism.",
|
||||||
|
"#"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"http://i.imgur.com/14F2Van.jpg",
|
||||||
|
"An image of the text \"Front End Development Certificate requirements\"",
|
||||||
|
"Let's confirm that you have completed all of our basic and intermediate Bonfires, and all our basic and intermediate Ziplines. Click the button below to verify this.",
|
||||||
|
"#"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"http://i.imgur.com/16SIhHO.jpg",
|
||||||
|
"An image of the word \"Congratulations\"",
|
||||||
|
"Congratulations! We've added your Front End Development Certificate to your certificate to your portfolio page. Unless you choose to hide your solutions, this certificate will remain publicly visible and verifiable.",
|
||||||
""
|
""
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
@ -6,12 +6,36 @@
|
|||||||
"id": "660add10cb82ac38a17513be",
|
"id": "660add10cb82ac38a17513be",
|
||||||
"title": "Claim Your Full Stack Development Certificate",
|
"title": "Claim Your Full Stack Development Certificate",
|
||||||
"difficulty": 0.00,
|
"difficulty": 0.00,
|
||||||
"challengeSeed": [],
|
"challengeSeed": [
|
||||||
|
{
|
||||||
|
"properties": ["isHonest", "isFullStackCert"],
|
||||||
|
"apis": ["/certificate/honest", "/certificate/verify/full-stack"],
|
||||||
|
"stepIndex": [1, 2]
|
||||||
|
}
|
||||||
|
],
|
||||||
"description": [
|
"description": [
|
||||||
[
|
[
|
||||||
"http://i.imgur.com/RlEk2IF.jpg",
|
"http://i.imgur.com/qXublEe.jpg",
|
||||||
"a picture of Free Code Camp's 4 benefits: Get connected, Learn JavaScript, Build your Portfolio, Help nonprofits",
|
"An image of our Full Stack Development Certificate",
|
||||||
"Welcome to Free Code Camp. We're an open source community of busy people who learn to code and help nonprofits.",
|
"This challenge will give you your verified Full Stack Development Certificate. Before we issue your certificate, we must verify that you have completed all of Bonfires, Ziplines and Basejumps. You must also accept our Academic Honesty Pledge. Click the button below to start this process.",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"http://i.imgur.com/HArFfMN.jpg",
|
||||||
|
"The definition of plagiarism: Plagiarism (noun) - copying someone else’s work and presenting it as your own without crediting them",
|
||||||
|
"By clicking below, you pledge that all of your submitted code A) is code you or your pair personally wrote, or B) comes from open source libraries like jQuery, or C) has been clearly attributed to its original authors. You also give us permission to audit your challenge solutions and revoke your certificate if we discover evidence of plagiarism.",
|
||||||
|
"#"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"http://i.imgur.com/2qn7tHp.jpg",
|
||||||
|
"An image of the text \"Full Stack Development Certificate requirements\"",
|
||||||
|
"Let's confirm that you have completed all of our Bonfires, Ziplines and Basejumps. Click the button below to verify this.",
|
||||||
|
"#"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"http://i.imgur.com/16SIhHO.jpg",
|
||||||
|
"An image of the word \"Congratulations\"",
|
||||||
|
"Congratulations! We've added your Full Stack Development Certificate to your certificate to your portfolio page. Unless you choose to hide your solutions, this certificate will remain publicly visible and verifiable.",
|
||||||
""
|
""
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
139
server/boot/certificate.js
Normal file
139
server/boot/certificate.js
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import dedent from 'dedent';
|
||||||
|
import { Observable } from 'rx';
|
||||||
|
import debugFactory from 'debug';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ifNoUser401,
|
||||||
|
ifNoUserSend
|
||||||
|
} from '../utils/middleware';
|
||||||
|
|
||||||
|
import {
|
||||||
|
saveUser,
|
||||||
|
observeQuery
|
||||||
|
} from '../utils/rx';
|
||||||
|
|
||||||
|
const frontEndChallangeId = '561add10cb82ac38a17513be';
|
||||||
|
const fullStackChallangeId = '660add10cb82ac38a17513be';
|
||||||
|
const debug = debugFactory('freecc:certification');
|
||||||
|
const sendMessageToNonUser = ifNoUserSend(
|
||||||
|
'must be logged in to complete.'
|
||||||
|
);
|
||||||
|
|
||||||
|
function isCertified(frontEndIds, { completedChallenges, isFrontEndCert }) {
|
||||||
|
if (isFrontEndCert) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return _.every(frontEndIds, ({ id }) => _.some(completedChallenges, { id }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function certificate(app) {
|
||||||
|
const router = app.loopback.Router();
|
||||||
|
const { Challenge } = app.models;
|
||||||
|
|
||||||
|
const frontEndChallangeIds$ = observeQuery(
|
||||||
|
Challenge,
|
||||||
|
'findById',
|
||||||
|
frontEndChallangeId,
|
||||||
|
{
|
||||||
|
tests: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.map(({ tests = [] }) => tests)
|
||||||
|
.shareReplay();
|
||||||
|
|
||||||
|
const fullStackChallangeIds$ = observeQuery(
|
||||||
|
Challenge,
|
||||||
|
'findById',
|
||||||
|
fullStackChallangeId,
|
||||||
|
{
|
||||||
|
tests: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.map(({ tests = [] }) => tests)
|
||||||
|
.shareReplay();
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/certificate/verify/front-end',
|
||||||
|
ifNoUser401,
|
||||||
|
verifyCert
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/certificate/verify/full-stack',
|
||||||
|
ifNoUser401,
|
||||||
|
verifyCert
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/certificate/honest',
|
||||||
|
sendMessageToNonUser,
|
||||||
|
postHonest
|
||||||
|
);
|
||||||
|
|
||||||
|
app.use(router);
|
||||||
|
|
||||||
|
function verifyCert(req, res, next) {
|
||||||
|
const isFront = req.path.split('/').pop() === 'front-end';
|
||||||
|
Observable.just({})
|
||||||
|
.flatMap(() => {
|
||||||
|
if (isFront) {
|
||||||
|
return frontEndChallangeIds$;
|
||||||
|
}
|
||||||
|
return fullStackChallangeIds$;
|
||||||
|
})
|
||||||
|
.flatMap((tests) => {
|
||||||
|
const { user } = req;
|
||||||
|
if (
|
||||||
|
isFront && !user.isFrontEndCert && isCertified(tests, user) ||
|
||||||
|
!isFront && !user.isFullStackCert && isCertified(tests, user)
|
||||||
|
) {
|
||||||
|
debug('certified');
|
||||||
|
if (isFront) {
|
||||||
|
user.isFrontEndCert = true;
|
||||||
|
user.completedChallenges.push({
|
||||||
|
completedDate: new Date(),
|
||||||
|
id: frontEndChallangeId
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
user.isFullStackCert = true;
|
||||||
|
user.completedChallenges.push({
|
||||||
|
completedDate: new Date(),
|
||||||
|
id: fullStackChallangeId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return saveUser(user);
|
||||||
|
}
|
||||||
|
return Observable.just(user);
|
||||||
|
})
|
||||||
|
.subscribe(
|
||||||
|
user => {
|
||||||
|
if (
|
||||||
|
isFront && user.isFrontEndCert ||
|
||||||
|
!isFront && user.isFullStackCert
|
||||||
|
) {
|
||||||
|
return res.status(200).send(true);
|
||||||
|
}
|
||||||
|
return res.status(200).send(
|
||||||
|
dedent`
|
||||||
|
Looks like you have not completed the neccessary steps,
|
||||||
|
Please return the map
|
||||||
|
`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
next
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function postHonest(req, res, next) {
|
||||||
|
const { user } = req;
|
||||||
|
user.isHonest = true;
|
||||||
|
saveUser(user)
|
||||||
|
.subscribe(
|
||||||
|
(user) => {
|
||||||
|
res.status(200).send(!!user.isHonest);
|
||||||
|
},
|
||||||
|
next
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -9,11 +9,10 @@ import utils from '../utils';
|
|||||||
import {
|
import {
|
||||||
saveUser,
|
saveUser,
|
||||||
observeMethod,
|
observeMethod,
|
||||||
observableQueryFromModel
|
observeQuery
|
||||||
} from '../utils/rx';
|
} from '../utils/rx';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
userMigration,
|
|
||||||
ifNoUserSend
|
ifNoUserSend
|
||||||
} from '../utils/middleware';
|
} from '../utils/middleware';
|
||||||
|
|
||||||
@ -147,8 +146,6 @@ module.exports = function(app) {
|
|||||||
completedBonfire
|
completedBonfire
|
||||||
);
|
);
|
||||||
|
|
||||||
// the follow routes are covered by userMigration
|
|
||||||
router.use(userMigration);
|
|
||||||
router.get('/map', challengeMap);
|
router.get('/map', challengeMap);
|
||||||
router.get(
|
router.get(
|
||||||
'/challenges/next-challenge',
|
'/challenges/next-challenge',
|
||||||
@ -330,7 +327,7 @@ module.exports = function(app) {
|
|||||||
challengeType: 5
|
challengeType: 5
|
||||||
};
|
};
|
||||||
|
|
||||||
observableQueryFromModel(
|
observeQuery(
|
||||||
User,
|
User,
|
||||||
'findOne',
|
'findOne',
|
||||||
{ where: { username: ('' + completedWith).toLowerCase() } }
|
{ where: { username: ('' + completedWith).toLowerCase() } }
|
||||||
@ -458,7 +455,7 @@ module.exports = function(app) {
|
|||||||
verified: false
|
verified: false
|
||||||
};
|
};
|
||||||
|
|
||||||
observableQueryFromModel(
|
observeQuery(
|
||||||
User,
|
User,
|
||||||
'findOne',
|
'findOne',
|
||||||
{ where: { username: completedWith.toLowerCase() } }
|
{ where: { username: completedWith.toLowerCase() } }
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { Observable } from 'rx';
|
||||||
import debugFactory from 'debug';
|
import debugFactory from 'debug';
|
||||||
|
|
||||||
import { ifNoUser401, ifNoUserRedirectTo } from '../utils/middleware';
|
import { ifNoUser401, ifNoUserRedirectTo } from '../utils/middleware';
|
||||||
|
import { observeQuery } from '../utils/rx';
|
||||||
|
|
||||||
const debug = debugFactory('freecc:boot:user');
|
const debug = debugFactory('freecc:boot:user');
|
||||||
const daysBetween = 1.5;
|
const daysBetween = 1.5;
|
||||||
@ -52,7 +55,16 @@ function dayDiff([head, tail]) {
|
|||||||
module.exports = function(app) {
|
module.exports = function(app) {
|
||||||
var router = app.loopback.Router();
|
var router = app.loopback.Router();
|
||||||
var User = app.models.User;
|
var User = app.models.User;
|
||||||
// var Story = app.models.Story;
|
function findUserByUsername$(username, fields) {
|
||||||
|
return observeQuery(
|
||||||
|
User,
|
||||||
|
'findOne',
|
||||||
|
{
|
||||||
|
where: { username },
|
||||||
|
fields
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
router.get('/login', function(req, res) {
|
router.get('/login', function(req, res) {
|
||||||
res.redirect(301, '/signin');
|
res.redirect(301, '/signin');
|
||||||
@ -85,7 +97,18 @@ module.exports = function(app) {
|
|||||||
);
|
);
|
||||||
router.get('/vote1', vote1);
|
router.get('/vote1', vote1);
|
||||||
router.get('/vote2', vote2);
|
router.get('/vote2', vote2);
|
||||||
// Ensure this is the last route!
|
|
||||||
|
// Ensure these are the last routes!
|
||||||
|
router.get(
|
||||||
|
'/:username/front-end-certification',
|
||||||
|
showCert
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/:username/full-stack-certification',
|
||||||
|
showCert
|
||||||
|
);
|
||||||
|
|
||||||
router.get('/:username', returnUser);
|
router.get('/:username', returnUser);
|
||||||
|
|
||||||
app.use(router);
|
app.use(router);
|
||||||
@ -184,14 +207,20 @@ module.exports = function(app) {
|
|||||||
return (obj.name || '').match(/^Waypoint/i);
|
return (obj.name || '').match(/^Waypoint/i);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
debug('user is fec', profileUser.isFrontEndCert);
|
||||||
res.render('account/show', {
|
res.render('account/show', {
|
||||||
title: 'Camper ' + profileUser.username + '\'s portfolio',
|
title: 'Camper ' + profileUser.username + '\'s portfolio',
|
||||||
username: profileUser.username,
|
username: profileUser.username,
|
||||||
name: profileUser.name,
|
name: profileUser.name,
|
||||||
|
|
||||||
isMigrationGrandfathered: profileUser.isMigrationGrandfathered,
|
isMigrationGrandfathered: profileUser.isMigrationGrandfathered,
|
||||||
isGithubCool: profileUser.isGithubCool,
|
isGithubCool: profileUser.isGithubCool,
|
||||||
isLocked: !!profileUser.isLocked,
|
isLocked: !!profileUser.isLocked,
|
||||||
|
|
||||||
|
isFrontEndCert: profileUser.isFrontEndCert,
|
||||||
|
isFullStackCert: profileUser.isFullStackCert,
|
||||||
|
isHonest: profileUser.isHonest,
|
||||||
|
|
||||||
location: profileUser.location,
|
location: profileUser.location,
|
||||||
calender: data,
|
calender: data,
|
||||||
|
|
||||||
@ -216,6 +245,64 @@ module.exports = function(app) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showCert(req, res, next) {
|
||||||
|
const username = req.params.username.toLowerCase();
|
||||||
|
const { user } = req;
|
||||||
|
const showFront = req.path.split('/').pop() === 'front-end-certification';
|
||||||
|
Observable.just(user)
|
||||||
|
.flatMap(user => {
|
||||||
|
if (user && user.username === username) {
|
||||||
|
return Observable.just(user);
|
||||||
|
}
|
||||||
|
return findUserByUsername$(username, {
|
||||||
|
isFrontEndCert: true,
|
||||||
|
isFullStackCert: true,
|
||||||
|
completedChallenges: true,
|
||||||
|
username: true,
|
||||||
|
name: true
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.subscribe(
|
||||||
|
(user) => {
|
||||||
|
if (!user) {
|
||||||
|
req.flash('errors', {
|
||||||
|
msg: `404: We couldn't find the user ${username}`
|
||||||
|
});
|
||||||
|
return res.redirect('/');
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
showFront && user.isFrontEndCert ||
|
||||||
|
!showFront && user.isFullStackCert
|
||||||
|
) {
|
||||||
|
var { completedDate } = _.find(user.completedChallenges, {
|
||||||
|
id: showFront ?
|
||||||
|
'561add10cb82ac38a17513be' :
|
||||||
|
'660add10cb82ac38a17513be'
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.render(
|
||||||
|
showFront ?
|
||||||
|
'certificate/front-end.jade' :
|
||||||
|
'certificate/full-stack.jade',
|
||||||
|
{
|
||||||
|
username: user.username,
|
||||||
|
date: moment(new Date(completedDate))
|
||||||
|
.format('MMMM, Do YYYY'),
|
||||||
|
name: user.name
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
req.flash('errors', {
|
||||||
|
msg: showFront ?
|
||||||
|
`Looks like user ${username} is not Front End certified` :
|
||||||
|
`Looks like user ${username} is not Full Stack certified`
|
||||||
|
});
|
||||||
|
res.redirect('/map');
|
||||||
|
},
|
||||||
|
next
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function toggleLockdownMode(req, res, next) {
|
function toggleLockdownMode(req, res, next) {
|
||||||
if (req.user.isLocked === true) {
|
if (req.user.isLocked === true) {
|
||||||
req.user.isLocked = false;
|
req.user.isLocked = false;
|
||||||
@ -297,11 +384,6 @@ module.exports = function(app) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* POST /forgot
|
|
||||||
* Create a random token, then the send user an email with a reset link.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function postForgot(req, res) {
|
function postForgot(req, res) {
|
||||||
const errors = req.validationErrors();
|
const errors = req.validationErrors();
|
||||||
const email = req.body.email.toLowerCase();
|
const email = req.body.email.toLowerCase();
|
||||||
|
@ -1,60 +1,24 @@
|
|||||||
var R = require('ramda');
|
export function ifNoUserRedirectTo(url) {
|
||||||
|
|
||||||
/*
|
|
||||||
* Middleware to migrate users from fragmented challenge structure to unified
|
|
||||||
* challenge structure
|
|
||||||
*
|
|
||||||
* @param req
|
|
||||||
* @param res
|
|
||||||
* @returns null
|
|
||||||
*/
|
|
||||||
exports.userMigration = function userMigration(req, res, next) {
|
|
||||||
if (!req.user || req.user.completedChallenges.length !== 0) {
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
req.user.completedChallenges = R.filter(function(elem) {
|
|
||||||
// getting rid of undefined
|
|
||||||
return elem;
|
|
||||||
}, R.concat(
|
|
||||||
req.user.completedCoursewares,
|
|
||||||
req.user.completedBonfires.map(function(bonfire) {
|
|
||||||
return ({
|
|
||||||
completedDate: bonfire.completedDate,
|
|
||||||
id: bonfire.id,
|
|
||||||
name: bonfire.name,
|
|
||||||
completedWith: bonfire.completedWith,
|
|
||||||
solution: bonfire.solution,
|
|
||||||
githubLink: '',
|
|
||||||
verified: false,
|
|
||||||
challengeType: 5
|
|
||||||
});
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return next();
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.ifNoUserRedirectTo = function ifNoUserRedirectTo(url) {
|
|
||||||
return function(req, res, next) {
|
return function(req, res, next) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
return res.redirect(url);
|
return res.redirect(url);
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
exports.ifNoUserSend = function ifNoUserSend(sendThis) {
|
export function ifNoUserSend(sendThis) {
|
||||||
return function(req, res, next) {
|
return function(req, res, next) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
return res.status(200).send(sendThis);
|
return res.status(200).send(sendThis);
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
exports.ifNoUser401 = function ifNoUser401(req, res, next) {
|
export function ifNoUser401(req, res, next) {
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
return res.status(401).end();
|
return res.status(401).end();
|
||||||
};
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
var Rx = require('rx');
|
import Rx from 'rx';
|
||||||
var debug = require('debug')('freecc:rxUtils');
|
import debugFactory from 'debug';
|
||||||
|
|
||||||
exports.saveInstance = function saveInstance(instance) {
|
const debug = debugFactory('freecc:rxUtils');
|
||||||
|
|
||||||
|
export function saveInstance(instance) {
|
||||||
return new Rx.Observable.create(function(observer) {
|
return new Rx.Observable.create(function(observer) {
|
||||||
if (!instance || typeof instance.save !== 'function') {
|
if (!instance || typeof instance.save !== 'function') {
|
||||||
debug('no instance or save method');
|
debug('no instance or save method');
|
||||||
@ -17,16 +19,15 @@ exports.saveInstance = function saveInstance(instance) {
|
|||||||
observer.onCompleted();
|
observer.onCompleted();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
// alias saveInstance
|
// alias saveInstance
|
||||||
exports.saveUser = exports.saveInstance;
|
export const saveUser = saveInstance;
|
||||||
|
|
||||||
exports.observeQuery = exports.observableQueryFromModel =
|
export function observeQuery(Model, method, query) {
|
||||||
function observableQueryFromModel(Model, method, query) {
|
|
||||||
return Rx.Observable.fromNodeCallback(Model[method], Model)(query);
|
return Rx.Observable.fromNodeCallback(Model[method], Model)(query);
|
||||||
};
|
}
|
||||||
|
|
||||||
exports.observeMethod = function observeMethod(context, methodName) {
|
export function observeMethod(context, methodName) {
|
||||||
return Rx.Observable.fromNodeCallback(context[methodName], context);
|
return Rx.Observable.fromNodeCallback(context[methodName], context);
|
||||||
};
|
}
|
||||||
|
@ -58,8 +58,13 @@ block content
|
|||||||
h1.flat-top.wrappable= name
|
h1.flat-top.wrappable= name
|
||||||
h1.flat-top.wrappable= location
|
h1.flat-top.wrappable= location
|
||||||
h1.flat-top.text-primary= "[ " + (progressTimestamps.length) + " ]"
|
h1.flat-top.text-primary= "[ " + (progressTimestamps.length) + " ]"
|
||||||
|
if isFrontEndCert
|
||||||
|
a.btn.btn-primary(href='/' + username + '/front-end-certification') View My Front End Development Certification
|
||||||
|
if isFullStackCert
|
||||||
|
.button-spacer
|
||||||
|
a.btn.btn-success(href='/' + username + '/full-stack-certification') View My Full Stack Development Certification
|
||||||
if (user && user.username !== username)
|
if (user && user.username !== username)
|
||||||
a.btn.btn-lg.btn-block.btn-twitter.btn-link-social(href='/link/twitter')
|
a.btn.btn-lg.btn-block.btn-twitter.btn-link-social(href='/leaderboard/add?username=#{username}')
|
||||||
i.fa.fa-plus-square
|
i.fa.fa-plus-square
|
||||||
| Add them to my personal leaderboard
|
| Add them to my personal leaderboard
|
||||||
|
|
||||||
|
45
server/views/certificate/font.jade
Normal file
45
server/views/certificate/font.jade
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
style.
|
||||||
|
@font-face {
|
||||||
|
font-family: "Sax Mono";
|
||||||
|
src: url("/fonts/saxmono.ttf") format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: inline-block;
|
||||||
|
font-family: "Sax Mono", monospace;
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.img-abs {
|
||||||
|
left 0;
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
width: 2000px
|
||||||
|
}
|
||||||
|
|
||||||
|
.cert-name {
|
||||||
|
font-size: 64px;
|
||||||
|
left: 1000px;
|
||||||
|
position: absolute;
|
||||||
|
top: 704px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cert-date {
|
||||||
|
font-size: 60px;
|
||||||
|
left: 760px;
|
||||||
|
position: absolute;
|
||||||
|
top: 1004.8px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cert-link {
|
||||||
|
font-size: 22px;
|
||||||
|
left: 120px;
|
||||||
|
position: absolute;
|
||||||
|
top: 1488px;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
6
server/views/certificate/front-end.jade
Normal file
6
server/views/certificate/front-end.jade
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
include font
|
||||||
|
#name.cert-name= name
|
||||||
|
img#cert.img-abs(src='http://i.imgur.com/ToFZKBd.jpg')
|
||||||
|
.cert-date= date
|
||||||
|
.cert-link verify this certification at: http://freecodecamp.com/#{username}/front-end-certification
|
||||||
|
include script
|
6
server/views/certificate/full-stack.jade
Normal file
6
server/views/certificate/full-stack.jade
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
include font
|
||||||
|
#name.cert-name= name
|
||||||
|
img#cert.img-abs(src='http://i.imgur.com/Z4PgjBQ.jpg')
|
||||||
|
.cert-date= date
|
||||||
|
.cert-link verify this certification at: http://freecodecamp.com/#{username}/full-stack-certification
|
||||||
|
include script
|
7
server/views/certificate/index.jade
Normal file
7
server/views/certificate/index.jade
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
extends ../layout
|
||||||
|
block content
|
||||||
|
.panel.panel-info
|
||||||
|
.panel-heading.text-center
|
||||||
|
h1 Certificate
|
||||||
|
.panel-body
|
||||||
|
p foo
|
8
server/views/certificate/script.jade
Normal file
8
server/views/certificate/script.jade
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
script.
|
||||||
|
(function() {
|
||||||
|
var containerWidth = document.getElementById('cert').offsetWidth;
|
||||||
|
var nameDiv = document.getElementById('name');
|
||||||
|
var nameWidth = nameDiv.offsetWidth;
|
||||||
|
console.log(containerWidth, nameWidth);
|
||||||
|
nameDiv.style.left = ((containerWidth - nameWidth) / 2) + 15;
|
||||||
|
})();
|
@ -9,7 +9,7 @@ block content
|
|||||||
.caption
|
.caption
|
||||||
p.large-p= step[2]
|
p.large-p= step[2]
|
||||||
if step[3]
|
if step[3]
|
||||||
a.btn.btn-block.btn-primary.challenge-step-btn-action(href='#{step[3]}' target='_blank') Go To Link
|
a.btn.btn-block.btn-primary.challenge-step-btn-action(id='#{index}' href='#{step[3]}' target='_blank') Go To Link
|
||||||
if index + 1 === description.length
|
if index + 1 === description.length
|
||||||
.btn.btn-block.btn-primary.challenge-step-btn-finish(id='last' class=step[3] ? 'disabled' : '') Finish challenge
|
.btn.btn-block.btn-primary.challenge-step-btn-finish(id='last' class=step[3] ? 'disabled' : '') Finish challenge
|
||||||
else
|
else
|
||||||
@ -32,8 +32,12 @@ block content
|
|||||||
a.btn.btn-lg.btn-primary.btn-block(href='/challenges/next-challenge?id=' + challengeId) Go to my next challenge
|
a.btn.btn-lg.btn-primary.btn-block(href='/challenges/next-challenge?id=' + challengeId) Go to my next challenge
|
||||||
script(src=rev('/js', 'commonFramework.js'))
|
script(src=rev('/js', 'commonFramework.js'))
|
||||||
script.
|
script.
|
||||||
var common = common || { init: [] };
|
var common = window.common || { init: [] };
|
||||||
common.challengeId = !{JSON.stringify(challengeId)};
|
common.challengeId = !{JSON.stringify(challengeId)};
|
||||||
common.challengeName = !{JSON.stringify(name)};
|
common.challengeName = !{JSON.stringify(name)};
|
||||||
common.challengeType = 7;
|
common.challengeType = 7;
|
||||||
common.dashedName = !{JSON.stringify(dashedName || '')};
|
common.dashedName = !{JSON.stringify(dashedName || '')};
|
||||||
|
common.isHonest = !{JSON.stringify(isHonest || false)};
|
||||||
|
common.isFrontEndCert = !{JSON.stringify(isFrontEndCert || false)};
|
||||||
|
common.isFullStackCert = !{JSON.stringify(isFullStackCert || false)};
|
||||||
|
common.challengeSeed = !{JSON.stringify(challengeSeed || [])};
|
||||||
|
Reference in New Issue
Block a user