Merge remote-tracking branch 'origin/staging' into curriculum/JSONAndAJAX
:ting with '#' will be ignored, and an empty message aborts : aad] dad
This commit is contained in:
@ -879,14 +879,53 @@ common.init.push((function() {
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function handleActionClick() {
|
||||
$(this)
|
||||
.parent()
|
||||
.find('.disabled')
|
||||
.removeClass('disabled');
|
||||
function handleActionClick(e) {
|
||||
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()
|
||||
.find('.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) {
|
||||
|
@ -102,14 +102,31 @@
|
||||
},
|
||||
"isLocked": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
"default": false,
|
||||
"description": "Campers profile does not show challenges/certificates to the public"
|
||||
},
|
||||
"currentChallenge": {
|
||||
"type": {}
|
||||
},
|
||||
"isUniqMigrated": {
|
||||
"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": {
|
||||
"type": [
|
||||
|
BIN
public/fonts/saxmono.ttf
Executable file
BIN
public/fonts/saxmono.ttf
Executable file
Binary file not shown.
@ -378,7 +378,7 @@
|
||||
"truncate(\"A-tisket a-tasket A green and yellow basket\", 11, \"\");"
|
||||
],
|
||||
"tests": [
|
||||
"assert(truncate(\"A-tisket a-tasket A green and yellow basket\", 11) === \"A-tisket...\", 'message: <code>truncate(\"A-tisket a-tasket A green and yellow basket\", 1)</code> should return \"A-tisket...\".');",
|
||||
"assert(truncate(\"A-tisket a-tasket A green and yellow basket\", 11) === \"A-tisket...\", 'message: <code>truncate(\"A-tisket a-tasket A green and yellow basket\", 11)</code> should return \"A-tisket...\".');",
|
||||
"assert(truncate(\"Peter Piper picked a peck of pickled peppers\", 14) === \"Peter Piper...\", 'message: <code>truncate(\"Peter Piper picked a peck of pickled peppers\", 14)</code> should return \"Peter Piper...\".');",
|
||||
"assert(truncate(\"A-tisket a-tasket A green and yellow basket\", \"A-tisket a-tasket A green and yellow basket\".length) === \"A-tisket a-tasket A green and yellow basket\", 'message: <code>truncate(\"A-tisket a-tasket A green and yellow basket\", \"A-tisket a-tasket A green and yellow basket\".length)</code> should return \"A-tisket a-tasket A green and yellow basket\".');",
|
||||
"assert(truncate('A-tisket a-tasket A green and yellow basket', 'A-tisket a-tasket A green and yellow basket'.length + 2) === 'A-tisket a-tasket A green and yellow basket', 'message: <code>truncate(\"A-tisket a-tasket A green and yellow basket\", \"A-tisket a-tasket A green and yellow basket\".length + 2)</code> should return \"A-tisket a-tasket A green and yellow basket\".');"
|
||||
|
@ -1039,7 +1039,7 @@
|
||||
],
|
||||
"tests":[
|
||||
"assert(test === 2, 'message: Your RegEx should have found two numbers in the <code>testString</code>.');",
|
||||
"assert(editor.getValue().match(/\\/\\\\d\\+\\//gi), 'message: You should be using the following expression <code>/\\\\d+/gi</code> to find the numbers in the <code>testString</code>.');"
|
||||
"assert(editor.getValue().match(/\\/\\\\d\\+\\//g), 'message: You should be using the following expression <code>/\\d+/g</code> to find the numbers in the <code>testString</code>.');"
|
||||
],
|
||||
"challengeSeed":[
|
||||
"var test = (function() {",
|
||||
@ -1047,7 +1047,7 @@
|
||||
"",
|
||||
" // Only change code below this line.",
|
||||
"",
|
||||
" var expression = /.+/gi;",
|
||||
" var expression = /.+/g;",
|
||||
"",
|
||||
" // Only change code above this line.",
|
||||
" // We use this function to show you the value of your variable in your output box.",
|
||||
@ -1069,7 +1069,7 @@
|
||||
],
|
||||
"tests":[
|
||||
"assert(test === 7, 'message: Your RegEx should have found seven spaces in the <code>testString</code>.');",
|
||||
"assert(editor.getValue().match(/\\/\\\\s\\+\\//gi), 'message: You should be using the following expression <code>/\\\\s+/gi</code> to find the spaces in the <code>testString</code>.');"
|
||||
"assert(editor.getValue().match(/\\/\\\\s\\+\\//g), 'message: You should be using the following expression <code>/\\s+/g</code> to find the spaces in the <code>testString</code>.');"
|
||||
],
|
||||
"challengeSeed":[
|
||||
"var test = (function(){",
|
||||
@ -1077,7 +1077,7 @@
|
||||
"",
|
||||
" // Only change code below this line.",
|
||||
"",
|
||||
" var expression = /.+/gi;",
|
||||
" var expression = /.+/g;",
|
||||
"",
|
||||
" // Only change code above this line.",
|
||||
" // We use this function to show you the value of your variable in your output box.",
|
||||
@ -1092,12 +1092,12 @@
|
||||
"title": "Invert Regular Expression Matches with JavaScript",
|
||||
"difficulty":"9.987",
|
||||
"description":[
|
||||
"Use <code>/\\S/gi</code> to match everything that isn't a space in the string.",
|
||||
"Use <code>/\\S/g</code> to match everything that isn't a space in the string.",
|
||||
"You can invert any match by using the uppercase version of the selector <code>\\s</code> versus <code>\\S</code> for example."
|
||||
],
|
||||
"tests":[
|
||||
"assert(test === 49, 'message: Your RegEx should have found forty nine non-space characters in the <code>testString</code>.');",
|
||||
"assert(editor.getValue().match(/\\/\\\\S\\/gi/gi), 'message: You should be using the following expression <code>/\\\\S/gi</code> to find non-space characters in the <code>testString</code>.');"
|
||||
"assert(editor.getValue().match(/\\/\\\\S\\/g/g), 'message: You should be using the following expression <code>/\\S/g</code> to find non-space characters in the <code>testString</code>.');"
|
||||
],
|
||||
"challengeSeed":[
|
||||
"var test = (function(){",
|
||||
@ -1105,7 +1105,7 @@
|
||||
"",
|
||||
" // Only change code below this line.",
|
||||
"",
|
||||
" var expression = /./gi;",
|
||||
" var expression = /./g;",
|
||||
"",
|
||||
" // Only change code above this line.",
|
||||
" // We use this function to show you the value of your variable in your output box.",
|
||||
|
@ -9,7 +9,7 @@
|
||||
"Now let's go back to our Cat Photo App. This time, we'll style it using the popular Bootstrap responsive CSS framework.",
|
||||
"Bootstrap will figure out how wide your screen is and respond by resizing your HTML elements - hence the name <code>Responsive Design</code>.",
|
||||
"With responsive design, there is no need to design a mobile version of your website. It will look good on devices with screens of any width.",
|
||||
"You can add Bootstrap to any app just by including it with <code><link rel=\"stylesheet\" href=\"//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css\"/></code> at the top of your HTML. But we've gone ahead and automatically added it to your Cat Photo App for you.",
|
||||
"You can add Bootstrap to any app just by including it with <code><link rel=\"stylesheet\" href=\"//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css\"/></code> at the top of your HTML. But we've added it for you to this page behind the scenes.",
|
||||
"To get started, we should nest all of our HTML in a <code>div</code> element with the class <code>container-fluid</code>."
|
||||
],
|
||||
"tests": [
|
||||
|
@ -5,13 +5,36 @@
|
||||
{
|
||||
"id": "561add10cb82ac38a17513be",
|
||||
"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": [
|
||||
[
|
||||
"http://i.imgur.com/RlEk2IF.jpg",
|
||||
"a picture of Free Code Camp's 4 benefits: Get connected, Learn JavaScript, Build your Portfolio, Help nonprofits",
|
||||
"Welcome to Free Code Camp. We're an open source community of busy people who learn to code and help nonprofits.",
|
||||
"http://i.imgur.com/luMkKst.jpg",
|
||||
"An image of our Front End Development Certificate",
|
||||
"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",
|
||||
"title": "Claim Your Full Stack Development Certificate",
|
||||
"difficulty": 0.00,
|
||||
"challengeSeed": [],
|
||||
"challengeSeed": [
|
||||
{
|
||||
"properties": ["isHonest", "isFullStackCert"],
|
||||
"apis": ["/certificate/honest", "/certificate/verify/full-stack"],
|
||||
"stepIndex": [1, 2]
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
[
|
||||
"http://i.imgur.com/RlEk2IF.jpg",
|
||||
"a picture of Free Code Camp's 4 benefits: Get connected, Learn JavaScript, Build your Portfolio, Help nonprofits",
|
||||
"Welcome to Free Code Camp. We're an open source community of busy people who learn to code and help nonprofits.",
|
||||
"http://i.imgur.com/qXublEe.jpg",
|
||||
"An image of our Full Stack Development Certificate",
|
||||
"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.",
|
||||
""
|
||||
]
|
||||
],
|
||||
|
@ -242,7 +242,7 @@
|
||||
[
|
||||
"",
|
||||
"",
|
||||
"Free Code Camp will always be free. If you want to feel more motivated to earn our certificates faster, we encourage you to instead donate each month to a nonprofit.",
|
||||
"Free Code Camp will always be free. If you want to feel more motivated to earn our certificates faster, we encourage you to instead pledge to donate to a nonprofit each day.",
|
||||
""
|
||||
]
|
||||
],
|
||||
|
151
server/boot/certificate.js
Normal file
151
server/boot/certificate.js
Normal file
@ -0,0 +1,151 @@
|
||||
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';
|
||||
|
||||
import {
|
||||
frontEndChallangeId,
|
||||
fullStackChallangeId
|
||||
} from '../utils/constantStrings.json';
|
||||
|
||||
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,
|
||||
{
|
||||
id: true,
|
||||
tests: true,
|
||||
name: true,
|
||||
challengeType: true
|
||||
}
|
||||
)
|
||||
.shareReplay();
|
||||
|
||||
const fullStackChallangeIds$ = observeQuery(
|
||||
Challenge,
|
||||
'findById',
|
||||
fullStackChallangeId,
|
||||
{
|
||||
id: true,
|
||||
tests: true,
|
||||
name: true,
|
||||
challengeType: true
|
||||
}
|
||||
)
|
||||
.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(challenge => {
|
||||
const { user } = req;
|
||||
const {
|
||||
id,
|
||||
tests,
|
||||
name,
|
||||
challengeType
|
||||
} = challenge;
|
||||
if (
|
||||
isFront && !user.isFrontEndCert && isCertified(tests, user) ||
|
||||
!isFront && !user.isFullStackCert && isCertified(tests, user)
|
||||
) {
|
||||
debug('certified');
|
||||
if (isFront) {
|
||||
user.isFrontEndCert = true;
|
||||
} else {
|
||||
user.isFullStackCert = true;
|
||||
}
|
||||
|
||||
user.completedChallenges.push({
|
||||
id,
|
||||
name,
|
||||
completedDate: new Date(),
|
||||
challengeType
|
||||
});
|
||||
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 {
|
||||
saveUser,
|
||||
observeMethod,
|
||||
observableQueryFromModel
|
||||
observeQuery
|
||||
} from '../utils/rx';
|
||||
|
||||
import {
|
||||
userMigration,
|
||||
ifNoUserSend
|
||||
} from '../utils/middleware';
|
||||
|
||||
@ -147,8 +146,6 @@ module.exports = function(app) {
|
||||
completedBonfire
|
||||
);
|
||||
|
||||
// the follow routes are covered by userMigration
|
||||
router.use(userMigration);
|
||||
router.get('/map', challengeMap);
|
||||
router.get(
|
||||
'/challenges/next-challenge',
|
||||
@ -183,9 +180,9 @@ module.exports = function(app) {
|
||||
'could not find challenge block for ' + challenge.block
|
||||
);
|
||||
}
|
||||
const nextBlock$ = blocks$.elementAt(blockIndex + 1);
|
||||
const firstChallengeOfNextBlock$ = nextBlock$
|
||||
.map(block => block.challenges[0]);
|
||||
const firstChallengeOfNextBlock$ = blocks$
|
||||
.elementAtOrDefault(blockIndex + 1, {})
|
||||
.map(({ challenges = [] }) => challenges[0]);
|
||||
|
||||
return blocks$
|
||||
.elementAt(blockIndex)
|
||||
@ -214,6 +211,9 @@ module.exports = function(app) {
|
||||
});
|
||||
})
|
||||
.map(nextChallenge => {
|
||||
if (!nextChallenge) {
|
||||
return null;
|
||||
}
|
||||
nextChallengeName = nextChallenge.dashedName;
|
||||
return nextChallengeName;
|
||||
})
|
||||
@ -270,12 +270,13 @@ module.exports = function(app) {
|
||||
}
|
||||
|
||||
if (dasherize(challenge.name) !== origChallengeName) {
|
||||
return Observable.just(
|
||||
'/challenges/' +
|
||||
dasherize(challenge.name) +
|
||||
'?solution=' +
|
||||
encodeURIComponent(solutionCode)
|
||||
);
|
||||
let redirectUrl = `/challenges/${dasherize(challenge.name)}`;
|
||||
|
||||
if (solutionCode) {
|
||||
redirectUrl += `?solution=${encodeURIComponent(solutionCode)}`;
|
||||
}
|
||||
|
||||
return Observable.just(redirectUrl);
|
||||
}
|
||||
|
||||
// save user does nothing if user does not exist
|
||||
@ -330,7 +331,7 @@ module.exports = function(app) {
|
||||
challengeType: 5
|
||||
};
|
||||
|
||||
observableQueryFromModel(
|
||||
observeQuery(
|
||||
User,
|
||||
'findOne',
|
||||
{ where: { username: ('' + completedWith).toLowerCase() } }
|
||||
@ -458,7 +459,7 @@ module.exports = function(app) {
|
||||
verified: false
|
||||
};
|
||||
|
||||
observableQueryFromModel(
|
||||
observeQuery(
|
||||
User,
|
||||
'findOne',
|
||||
{ where: { username: completedWith.toLowerCase() } }
|
||||
|
15
server/boot/commit.js
Normal file
15
server/boot/commit.js
Normal file
@ -0,0 +1,15 @@
|
||||
export default function commit(app) {
|
||||
const router = app.loopback.Router();
|
||||
router.get(
|
||||
'/commit',
|
||||
commitToNonprofit
|
||||
);
|
||||
|
||||
app.use(router);
|
||||
|
||||
function commitToNonprofit(req, res) {
|
||||
res.render('commit/', {
|
||||
title: 'Commit to a nonprofit. Commit to your goal.'
|
||||
});
|
||||
}
|
||||
}
|
@ -1,8 +1,15 @@
|
||||
import _ from 'lodash';
|
||||
import dedent from 'dedent';
|
||||
import moment from 'moment';
|
||||
import { Observable } from 'rx';
|
||||
import debugFactory from 'debug';
|
||||
|
||||
import {
|
||||
frontEndChallangeId,
|
||||
fullStackChallangeId
|
||||
} from '../utils/constantStrings.json';
|
||||
import { ifNoUser401, ifNoUserRedirectTo } from '../utils/middleware';
|
||||
import { observeQuery } from '../utils/rx';
|
||||
|
||||
const debug = debugFactory('freecc:boot:user');
|
||||
const daysBetween = 1.5;
|
||||
@ -52,7 +59,16 @@ function dayDiff([head, tail]) {
|
||||
module.exports = function(app) {
|
||||
var router = app.loopback.Router();
|
||||
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) {
|
||||
res.redirect(301, '/signin');
|
||||
@ -85,7 +101,18 @@ module.exports = function(app) {
|
||||
);
|
||||
router.get('/vote1', vote1);
|
||||
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);
|
||||
|
||||
app.use(router);
|
||||
@ -184,14 +211,20 @@ module.exports = function(app) {
|
||||
return (obj.name || '').match(/^Waypoint/i);
|
||||
});
|
||||
|
||||
debug('user is fec', profileUser.isFrontEndCert);
|
||||
res.render('account/show', {
|
||||
title: 'Camper ' + profileUser.username + '\'s portfolio',
|
||||
username: profileUser.username,
|
||||
name: profileUser.name,
|
||||
|
||||
isMigrationGrandfathered: profileUser.isMigrationGrandfathered,
|
||||
isGithubCool: profileUser.isGithubCool,
|
||||
isLocked: !!profileUser.isLocked,
|
||||
|
||||
isFrontEndCert: profileUser.isFrontEndCert,
|
||||
isFullStackCert: profileUser.isFullStackCert,
|
||||
isHonest: profileUser.isHonest,
|
||||
|
||||
location: profileUser.location,
|
||||
calender: data,
|
||||
|
||||
@ -216,6 +249,92 @@ 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 (!user.isGithubCool) {
|
||||
req.flash('errors', {
|
||||
msg: dedent`
|
||||
This user needs to link GitHub with their account
|
||||
in order to display this certificate to the public.
|
||||
`
|
||||
});
|
||||
return res.redirect('back');
|
||||
}
|
||||
if (user.isLocked) {
|
||||
req.flash('errors', {
|
||||
msg: dedent`
|
||||
${username} has chosen to hide their work from the public.
|
||||
They need to unhide their work in order for this certificate to
|
||||
be verifiable.
|
||||
`
|
||||
});
|
||||
return res.redirect('back');
|
||||
}
|
||||
if (!user.isHonest) {
|
||||
req.flash('errors', {
|
||||
msg: dedent`
|
||||
${username} has not agreed to our Academic Honesty Pledge yet.
|
||||
`
|
||||
});
|
||||
return res.redirect('back');
|
||||
}
|
||||
|
||||
if (
|
||||
showFront && user.isFrontEndCert ||
|
||||
!showFront && user.isFullStackCert
|
||||
) {
|
||||
var { completedDate } = _.find(user.completedChallenges, {
|
||||
id: showFront ?
|
||||
frontEndChallangeId :
|
||||
fullStackChallangeId
|
||||
});
|
||||
|
||||
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) {
|
||||
if (req.user.isLocked === true) {
|
||||
req.user.isLocked = false;
|
||||
@ -297,11 +416,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) {
|
||||
const errors = req.validationErrors();
|
||||
const email = req.body.email.toLowerCase();
|
||||
|
@ -1,3 +1,5 @@
|
||||
{
|
||||
"gitHubUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1521.3 Safari/537.36"
|
||||
"gitHubUserAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1521.3 Safari/537.36",
|
||||
"frontEndChallangeId": "561add10cb82ac38a17513be",
|
||||
"fullStackChallangeId": "660add10cb82ac38a17513be"
|
||||
}
|
||||
|
@ -1,60 +1,24 @@
|
||||
var R = require('ramda');
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
export function ifNoUserRedirectTo(url) {
|
||||
return function(req, res, next) {
|
||||
if (req.user) {
|
||||
return next();
|
||||
}
|
||||
return res.redirect(url);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
exports.ifNoUserSend = function ifNoUserSend(sendThis) {
|
||||
export function ifNoUserSend(sendThis) {
|
||||
return function(req, res, next) {
|
||||
if (req.user) {
|
||||
return next();
|
||||
}
|
||||
return res.status(200).send(sendThis);
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
exports.ifNoUser401 = function ifNoUser401(req, res, next) {
|
||||
export function ifNoUser401(req, res, next) {
|
||||
if (req.user) {
|
||||
return next();
|
||||
}
|
||||
return res.status(401).end();
|
||||
};
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
var Rx = require('rx');
|
||||
var debug = require('debug')('freecc:rxUtils');
|
||||
import Rx from 'rx';
|
||||
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) {
|
||||
if (!instance || typeof instance.save !== 'function') {
|
||||
debug('no instance or save method');
|
||||
@ -17,16 +19,15 @@ exports.saveInstance = function saveInstance(instance) {
|
||||
observer.onCompleted();
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// alias saveInstance
|
||||
exports.saveUser = exports.saveInstance;
|
||||
export const saveUser = saveInstance;
|
||||
|
||||
exports.observeQuery = exports.observableQueryFromModel =
|
||||
function observableQueryFromModel(Model, method, query) {
|
||||
return Rx.Observable.fromNodeCallback(Model[method], Model)(query);
|
||||
};
|
||||
export function observeQuery(Model, method, 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);
|
||||
};
|
||||
}
|
||||
|
@ -58,8 +58,13 @@ block content
|
||||
h1.flat-top.wrappable= name
|
||||
h1.flat-top.wrappable= location
|
||||
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)
|
||||
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
|
||||
| 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;
|
||||
})();
|
54
server/views/commit/index.jade
Normal file
54
server/views/commit/index.jade
Normal file
@ -0,0 +1,54 @@
|
||||
extends ../layout
|
||||
block content
|
||||
.panel.panel-info
|
||||
.panel-body
|
||||
h3.text-center Commit to yourself. Commit to a nonprofit.
|
||||
.col-xs-12.col-sm-6.col-sm-offset-3
|
||||
p Are you looking for a burst of motivation? Do you want to help nonprofits before you’re ready to code for them? You can do both by pledging a monthly donation to a nonprofit until you've earned either your Front End or Full Stack Development certificate. Join Commit below or click "maybe later".
|
||||
.col-xs-12.col-sm-6.col-sm-offset-3
|
||||
h4 Step 1: Choose your goal
|
||||
.radio
|
||||
label
|
||||
input(type='radio' id='front-end-development-certificate' name='goal')
|
||||
| Front End Development Certificate (takes about 400 hours)
|
||||
.radio
|
||||
label
|
||||
input(type='radio' id='full-stack-development-certificate' name='goal')
|
||||
| Full Stack Development Certificate (takes about 800 hours)
|
||||
.spacer
|
||||
h4 Step 2: Choose one of our nonprofits
|
||||
.row
|
||||
.col-xs-12.col-sm-6
|
||||
a(href="http://i.imgur.com/U1CyEuA.jpg" data-lightbox="img-enlarge")
|
||||
img.img-responsive(src='http://i.imgur.com/U1CyEuA.jpg' alt="Girl Develop It participants coding at tables.")
|
||||
.radio
|
||||
label
|
||||
input(type='radio' id='girl-develop-it' name='nonprofit')
|
||||
| Girl Develop It is a nonprofit that provides in-person classes for women to learn to code.
|
||||
.col-xs-12.col-sm-6
|
||||
a(href="http://i.imgur.com/NERytFF.jpg" data-lightbox="img-enlarge")
|
||||
img.img-responsive(src='http://i.imgur.com/NERytFF.jpg' alt="Vets in Tech participants standing together at a conference.")
|
||||
.radio
|
||||
label
|
||||
input(type='radio' id='vets-in-tech' name='nonprofit')
|
||||
| Vets in Tech is a nonprofit that helps veterans prepare for tech jobs.
|
||||
.spacer
|
||||
h4 Step 3: Choose your monthly pledge
|
||||
.radio
|
||||
label
|
||||
input(type='radio' id='5-dollar-pledge' name='pledge-amount')
|
||||
| $5 per month
|
||||
.radio
|
||||
label
|
||||
input(type='radio' id='10-dollar-pledge' name='pledge-amount')
|
||||
| $10 per month
|
||||
.radio
|
||||
label
|
||||
input(type='radio' id='50-dollar-pledge' name='pledge-amount')
|
||||
| $50 per month
|
||||
|
||||
.spacer
|
||||
a.button.btn.btn-block.btn-primary(href='https://www.paypal.com/us/cgi-bin/webscr?cmd=_flow&SESSION=T3x0DY-bLMFXuhmjYZXs-BhmDoiXfuNh5BWad5VBcMomkkDSZY0b_-_W3HS&dispatch=5885d80a13c0db1f8e263663d3faee8d0b9dcb01a9b6dc564e45f62871326a5e') Commit
|
||||
.button-spacer
|
||||
a.button.btn.btn-block.btn-warning(href='/') Maybe later
|
||||
.spacer
|
@ -9,7 +9,7 @@ block content
|
||||
.caption
|
||||
p.large-p= step[2]
|
||||
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
|
||||
.btn.btn-block.btn-primary.challenge-step-btn-finish(id='last' class=step[3] ? 'disabled' : '') Finish challenge
|
||||
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
|
||||
script(src=rev('/js', 'commonFramework.js'))
|
||||
script.
|
||||
var common = common || { init: [] };
|
||||
var common = window.common || { init: [] };
|
||||
common.challengeId = !{JSON.stringify(challengeId)};
|
||||
common.challengeName = !{JSON.stringify(name)};
|
||||
common.challengeType = 7;
|
||||
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