Merge branch 'staging' into fix/normalize-flash-type
This commit is contained in:
15
server/boot/a-extend-built-ins.js
Normal file
15
server/boot/a-extend-built-ins.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Observable } from 'rx';
|
||||
|
||||
export default function extendEmail(app) {
|
||||
const { AccessToken, Email } = app.models;
|
||||
Email.send$ = Observable.fromNodeCallback(Email.send, Email);
|
||||
AccessToken.findOne$ = Observable.fromNodeCallback(
|
||||
AccessToken.findOne.bind(AccessToken)
|
||||
);
|
||||
AccessToken.prototype.validate$ = Observable.fromNodeCallback(
|
||||
AccessToken.prototype.validate
|
||||
);
|
||||
AccessToken.prototype.destroy$ = Observable.fromNodeCallback(
|
||||
AccessToken.prototype.destroy
|
||||
);
|
||||
}
|
@@ -1,6 +0,0 @@
|
||||
import { Observable } from 'rx';
|
||||
|
||||
export default function extendEmail(app) {
|
||||
const { Email } = app.models;
|
||||
Email.send$ = Observable.fromNodeCallback(Email.send, Email);
|
||||
}
|
@@ -1,4 +1,229 @@
|
||||
import _ from 'lodash';
|
||||
import { Observable } from 'rx';
|
||||
import dedent from 'dedent';
|
||||
// import debugFactory from 'debug';
|
||||
import { isEmail } from 'validator';
|
||||
import { check, validationResult } from 'express-validator/check';
|
||||
|
||||
import { ifUserRedirectTo } from '../utils/middleware';
|
||||
import {
|
||||
wrapHandledError,
|
||||
createValidatorErrorFormatter
|
||||
} from '../utils/create-handled-error.js';
|
||||
|
||||
const isSignUpDisabled = !!process.env.DISABLE_SIGNUP;
|
||||
// const debug = debugFactory('fcc:boot:auth');
|
||||
if (isSignUpDisabled) {
|
||||
console.log('fcc:boot:auth - Sign up is disabled');
|
||||
}
|
||||
|
||||
module.exports = function enableAuthentication(app) {
|
||||
// enable authentication
|
||||
// enable loopback access control authentication. see:
|
||||
// loopback.io/doc/en/lb2/Authentication-authorization-and-permissions.html
|
||||
app.enableAuth();
|
||||
const ifUserRedirect = ifUserRedirectTo();
|
||||
const router = app.loopback.Router();
|
||||
const api = app.loopback.Router();
|
||||
const { AuthToken, User } = app.models;
|
||||
|
||||
router.get('/login', (req, res) => res.redirect(301, '/signin'));
|
||||
router.get('/logout', (req, res) => res.redirect(301, '/signout'));
|
||||
|
||||
function getEmailSignin(req, res) {
|
||||
if (isSignUpDisabled) {
|
||||
return res.render('account/beta', {
|
||||
title: 'New sign ups are disabled'
|
||||
});
|
||||
}
|
||||
return res.render('account/email-signin', {
|
||||
title: 'Sign in to freeCodeCamp using your Email Address'
|
||||
});
|
||||
}
|
||||
|
||||
router.get('/signup', ifUserRedirect, getEmailSignin);
|
||||
router.get('/signin', ifUserRedirect, getEmailSignin);
|
||||
router.get('/email-signin', ifUserRedirect, getEmailSignin);
|
||||
|
||||
router.get('/signout', (req, res) => {
|
||||
req.logout();
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
|
||||
router.get(
|
||||
'/deprecated-signin',
|
||||
ifUserRedirect,
|
||||
(req, res) => res.render('account/deprecated-signin', {
|
||||
title: 'Sign in to freeCodeCamp using a Deprecated Login'
|
||||
})
|
||||
);
|
||||
|
||||
const defaultErrorMsg = dedent`
|
||||
Oops, something is not right,
|
||||
please request a fresh link to sign in / sign up.
|
||||
`;
|
||||
|
||||
const passwordlessGetValidators = [
|
||||
check('email')
|
||||
.isBase64()
|
||||
.withMessage('Email should be a base64 encoded string.'),
|
||||
check('token')
|
||||
.exists()
|
||||
.withMessage('Token should exist.')
|
||||
// based on strongloop/loopback/common/models/access-token.js#L15
|
||||
.isLength({ min: 64, max: 64 })
|
||||
.withMessage('Token is not the right length.')
|
||||
];
|
||||
|
||||
function getPasswordlessAuth(req, res, next) {
|
||||
const {
|
||||
query: {
|
||||
email: encodedEmail,
|
||||
token: authTokenId
|
||||
} = {}
|
||||
} = req;
|
||||
const validation = validationResult(req)
|
||||
.formatWith(createValidatorErrorFormatter('errors', '/email-signup'));
|
||||
|
||||
if (!validation.isEmpty()) {
|
||||
const errors = validation.array();
|
||||
return next(errors.pop());
|
||||
}
|
||||
|
||||
const email = User.decodeEmail(encodedEmail);
|
||||
if (!isEmail(email)) {
|
||||
return next(wrapHandledError(
|
||||
new TypeError('decoded email is invalid'),
|
||||
{
|
||||
type: 'info',
|
||||
message: 'The email encoded in the link is incorrectly formatted',
|
||||
redirectTo: '/email-sign'
|
||||
}
|
||||
));
|
||||
}
|
||||
// first find
|
||||
return AuthToken.findOne$({ where: { id: authTokenId } })
|
||||
.flatMap(authToken => {
|
||||
if (!authToken) {
|
||||
throw wrapHandledError(
|
||||
new Error(`no token found for id: ${authTokenId}`),
|
||||
{
|
||||
type: 'info',
|
||||
message: defaultErrorMsg,
|
||||
redirectTo: '/email-signin'
|
||||
}
|
||||
);
|
||||
}
|
||||
// find user then validate and destroy email validation token
|
||||
// finally retun user instance
|
||||
return User.findOne$({ where: { id: authToken.userId } })
|
||||
.flatMap(user => {
|
||||
if (!user) {
|
||||
throw wrapHandledError(
|
||||
new Error(`no user found for token: ${authTokenId}`),
|
||||
{
|
||||
type: 'info',
|
||||
message: defaultErrorMsg,
|
||||
redirectTo: '/email-signin'
|
||||
}
|
||||
);
|
||||
}
|
||||
if (user.email !== email) {
|
||||
throw wrapHandledError(
|
||||
new Error('user email does not match'),
|
||||
{
|
||||
type: 'info',
|
||||
message: defaultErrorMsg,
|
||||
redirectTo: '/email-signin'
|
||||
}
|
||||
);
|
||||
}
|
||||
return authToken.validate()
|
||||
.map(isValid => {
|
||||
if (!isValid) {
|
||||
throw wrapHandledError(
|
||||
new Error('token is invalid'),
|
||||
{
|
||||
type: 'info',
|
||||
message: `
|
||||
Looks like the link you clicked has expired,
|
||||
please request a fresh link, to sign in.
|
||||
`,
|
||||
redirectTo: '/email-signin'
|
||||
}
|
||||
);
|
||||
}
|
||||
return authToken.destroy();
|
||||
})
|
||||
.map(() => user);
|
||||
});
|
||||
})
|
||||
// at this point token has been validated and destroyed
|
||||
// update user and log them in
|
||||
.map(user => user.loginByRequest(req, res))
|
||||
.do(() => {
|
||||
let redirectTo = '/';
|
||||
|
||||
if (
|
||||
req.session &&
|
||||
req.session.returnTo
|
||||
) {
|
||||
redirectTo = req.session.returnTo;
|
||||
}
|
||||
|
||||
req.flash('success', { msg:
|
||||
'Success! You have signed in to your account. Happy Coding!'
|
||||
});
|
||||
|
||||
return res.redirect(redirectTo);
|
||||
})
|
||||
.subscribe(
|
||||
() => {},
|
||||
next
|
||||
);
|
||||
}
|
||||
|
||||
router.get(
|
||||
'/passwordless-auth',
|
||||
ifUserRedirect,
|
||||
passwordlessGetValidators,
|
||||
getPasswordlessAuth
|
||||
);
|
||||
|
||||
const passwordlessPostValidators = [
|
||||
check('email')
|
||||
.isEmail()
|
||||
.withMessage('Email is not a valid email address.')
|
||||
];
|
||||
function postPasswordlessAuth(req, res, next) {
|
||||
const { body: { email } = {} } = req;
|
||||
const validation = validationResult(req)
|
||||
.formatWith(createValidatorErrorFormatter('errors', '/email-signup'));
|
||||
if (!validation.isEmpty()) {
|
||||
const errors = validation.array();
|
||||
return next(errors.pop());
|
||||
}
|
||||
|
||||
return User.findOne$({ where: { email } })
|
||||
.flatMap(_user => Observable.if(
|
||||
// if no user found create new user and save to db
|
||||
_.constant(_user),
|
||||
Observable.of(_user),
|
||||
User.create$({ email })
|
||||
)
|
||||
.flatMap(user => user.requestAuthEmail(!_user))
|
||||
)
|
||||
.do(msg => res.status(200).send({ message: msg }))
|
||||
.subscribe(_.noop, next);
|
||||
}
|
||||
|
||||
api.post(
|
||||
'/passwordless-auth',
|
||||
ifUserRedirect,
|
||||
passwordlessPostValidators,
|
||||
postPasswordlessAuth
|
||||
);
|
||||
|
||||
app.use('/:lang', router);
|
||||
app.use(api);
|
||||
};
|
||||
|
@@ -14,9 +14,14 @@ import {
|
||||
import { observeQuery } from '../utils/rx';
|
||||
|
||||
import {
|
||||
respWebDesignId,
|
||||
frontEndLibsId,
|
||||
jsAlgoDataStructId,
|
||||
frontEndChallengeId,
|
||||
dataVisChallengeId,
|
||||
backEndChallengeId
|
||||
dataVisId,
|
||||
apisMicroservicesId,
|
||||
backEndChallengeId,
|
||||
infosecQaId
|
||||
} from '../utils/constantStrings.json';
|
||||
|
||||
import {
|
||||
@@ -60,9 +65,12 @@ function getIdsForCert$(id, Challenge) {
|
||||
// {
|
||||
// email: String,
|
||||
// username: String,
|
||||
// isFrontEndCert: Boolean,
|
||||
// isBackEndCert: Boolean,
|
||||
// isDataVisCert: Boolean
|
||||
// isRespWebDesignCert: Boolean,
|
||||
// isFrontEndLibsCert: Boolean,
|
||||
// isJsAlgoDataStructCert: Boolean,
|
||||
// isDataVisCert: Boolean,
|
||||
// isApisMicroservicesCert: Boolean,
|
||||
// isInfosecQaCert: Boolean
|
||||
// },
|
||||
// send$: Observable
|
||||
// ) => Observable
|
||||
@@ -71,17 +79,23 @@ function sendCertifiedEmail(
|
||||
email,
|
||||
name,
|
||||
username,
|
||||
isFrontEndCert,
|
||||
isBackEndCert,
|
||||
isDataVisCert
|
||||
isRespWebDesignCert,
|
||||
isFrontEndLibsCert,
|
||||
isJsAlgoDataStructCert,
|
||||
isDataVisCert,
|
||||
isApisMicroservicesCert,
|
||||
isInfosecQaCert
|
||||
},
|
||||
send$
|
||||
) {
|
||||
if (
|
||||
!isEmail(email) ||
|
||||
!isFrontEndCert ||
|
||||
!isBackEndCert ||
|
||||
!isDataVisCert
|
||||
!isRespWebDesignCert ||
|
||||
!isFrontEndLibsCert ||
|
||||
!isJsAlgoDataStructCert ||
|
||||
!isDataVisCert ||
|
||||
!isApisMicroservicesCert ||
|
||||
!isInfosecQaCert
|
||||
) {
|
||||
return Observable.just(false);
|
||||
}
|
||||
@@ -107,8 +121,16 @@ export default function certificate(app) {
|
||||
|
||||
const certTypeIds = {
|
||||
[certTypes.frontEnd]: getIdsForCert$(frontEndChallengeId, Challenge),
|
||||
[certTypes.dataVis]: getIdsForCert$(dataVisChallengeId, Challenge),
|
||||
[certTypes.backEnd]: getIdsForCert$(backEndChallengeId, Challenge)
|
||||
[certTypes.backEnd]: getIdsForCert$(backEndChallengeId, Challenge),
|
||||
[certTypes.respWebDesign]: getIdsForCert$(respWebDesignId, Challenge),
|
||||
[certTypes.frontEndLibs]: getIdsForCert$(frontEndLibsId, Challenge),
|
||||
[certTypes.jsAlgoDataStruct]: getIdsForCert$(jsAlgoDataStructId, Challenge),
|
||||
[certTypes.dataVis]: getIdsForCert$(dataVisId, Challenge),
|
||||
[certTypes.apisMicroservices]: getIdsForCert$(
|
||||
apisMicroservicesId,
|
||||
Challenge
|
||||
),
|
||||
[certTypes.infosecQa]: getIdsForCert$(infosecQaId, Challenge)
|
||||
};
|
||||
|
||||
router.post(
|
||||
@@ -123,12 +145,42 @@ export default function certificate(app) {
|
||||
verifyCert.bind(null, certTypes.backEnd)
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/certificate/verify/responsive-web-design',
|
||||
ifNoUser401,
|
||||
verifyCert.bind(null, certTypes.respWebDesign)
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/certificate/verify/front-end-libraries',
|
||||
ifNoUser401,
|
||||
verifyCert.bind(null, certTypes.frontEndLibs)
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/certificate/verify/javascript-algorithms-data-structures',
|
||||
ifNoUser401,
|
||||
verifyCert.bind(null, certTypes.jsAlgoDataStruct)
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/certificate/verify/data-visualization',
|
||||
ifNoUser401,
|
||||
verifyCert.bind(null, certTypes.dataVis)
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/certificate/verify/apis-microservices',
|
||||
ifNoUser401,
|
||||
verifyCert.bind(null, certTypes.apisMicroservices)
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/certificate/verify/information-security-quality-assurance',
|
||||
ifNoUser401,
|
||||
verifyCert.bind(null, certTypes.infosecQa)
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/certificate/honest',
|
||||
sendMessageToNonUser,
|
||||
|
@@ -132,7 +132,7 @@ export default function commit(app) {
|
||||
const {
|
||||
nonprofit: nonprofitName = 'girl develop it',
|
||||
amount = '5',
|
||||
goal = commitGoals.frontEndCert
|
||||
goal = commitGoals.respWebDesignCert
|
||||
} = req.query;
|
||||
|
||||
const nonprofit = findNonprofit(nonprofitName);
|
||||
|
@@ -1,15 +1,19 @@
|
||||
const createDebugger = require('debug');
|
||||
|
||||
const log = createDebugger('fcc:boot:explorer');
|
||||
|
||||
module.exports = function mountLoopBackExplorer(app) {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
return;
|
||||
}
|
||||
var explorer;
|
||||
let explorer;
|
||||
try {
|
||||
explorer = require('loopback-component-explorer');
|
||||
} catch (err) {
|
||||
// Print the message only when the app was started via `app.listen()`.
|
||||
// Do not print any message when the project is used as a component.
|
||||
app.once('started', function() {
|
||||
console.log(
|
||||
log(
|
||||
'Run `npm install loopback-component-explorer` to enable ' +
|
||||
'the LoopBack explorer'
|
||||
);
|
||||
@@ -17,13 +21,13 @@ module.exports = function mountLoopBackExplorer(app) {
|
||||
return;
|
||||
}
|
||||
|
||||
var restApiRoot = app.get('restApiRoot');
|
||||
var mountPath = '/explorer';
|
||||
const restApiRoot = app.get('restApiRoot');
|
||||
const mountPath = '/explorer';
|
||||
|
||||
explorer(app, { basePath: restApiRoot, mountPath });
|
||||
app.once('started', function() {
|
||||
var baseUrl = app.get('url').replace(/\/$/, '');
|
||||
const baseUrl = app.get('url').replace(/\/$/, '');
|
||||
|
||||
console.log('Browse your REST API at %s%s', baseUrl, mountPath);
|
||||
log('Browse your REST API at %s%s', baseUrl, mountPath);
|
||||
});
|
||||
};
|
||||
|
@@ -1,35 +0,0 @@
|
||||
var path = require('path');
|
||||
var loopback = require('loopback');
|
||||
var express = require('express');
|
||||
|
||||
var port = 1337;
|
||||
|
||||
// this will listen to traffic on port 1337
|
||||
// The purpose is to redirect any user who is direct to https
|
||||
// instead of http by mistake. Our nginx proxy server will listen
|
||||
// for https traffic and serve from this port on this server.
|
||||
// the view being send will have a short timeout and a redirect
|
||||
module.exports = function(loopbackApp) {
|
||||
var app = express();
|
||||
app.set('view engine', 'jade');
|
||||
// views in ../views'
|
||||
app.set('views', path.join(__dirname, '..'));
|
||||
|
||||
// server static files
|
||||
app.use(loopback.static(path.join(
|
||||
__dirname,
|
||||
'../',
|
||||
'../public'
|
||||
)));
|
||||
|
||||
// all traffic will be redirected on page load;
|
||||
app.use(function(req, res) {
|
||||
return res.render('views/redirect-https');
|
||||
});
|
||||
|
||||
loopbackApp.once('started', function() {
|
||||
app.listen(port, function() {
|
||||
console.log('https redirect listening on port %s', port);
|
||||
});
|
||||
});
|
||||
};
|
@@ -6,8 +6,13 @@ import emoji from 'node-emoji';
|
||||
|
||||
import {
|
||||
frontEndChallengeId,
|
||||
dataVisChallengeId,
|
||||
backEndChallengeId
|
||||
backEndChallengeId,
|
||||
respWebDesignId,
|
||||
frontEndLibsId,
|
||||
jsAlgoDataStructId,
|
||||
dataVisId,
|
||||
apisMicroservicesId,
|
||||
infosecQaId
|
||||
} from '../utils/constantStrings.json';
|
||||
import certTypes from '../utils/certTypes.json';
|
||||
import {
|
||||
@@ -24,27 +29,44 @@ import {
|
||||
import supportedLanguages from '../../common/utils/supported-languages';
|
||||
import { getChallengeInfo, cachedMap } from '../utils/map';
|
||||
|
||||
const isSignUpDisabled = !!process.env.DISABLE_SIGNUP;
|
||||
const debug = debugFactory('fcc:boot:user');
|
||||
const sendNonUserToMap = ifNoUserRedirectTo('/map');
|
||||
const certIds = {
|
||||
[certTypes.frontEnd]: frontEndChallengeId,
|
||||
[certTypes.dataVis]: dataVisChallengeId,
|
||||
[certTypes.backEnd]: backEndChallengeId
|
||||
[certTypes.backEnd]: backEndChallengeId,
|
||||
[certTypes.respWebDesign]: respWebDesignId,
|
||||
[certTypes.frontEndLibs]: frontEndLibsId,
|
||||
[certTypes.jsAlgoDataStruct]: jsAlgoDataStructId,
|
||||
[certTypes.dataVis]: dataVisId,
|
||||
[certTypes.apisMicroservices]: apisMicroservicesId,
|
||||
[certTypes.infosecQa]: infosecQaId
|
||||
};
|
||||
|
||||
const certViews = {
|
||||
[certTypes.frontEnd]: 'certificate/front-end.jade',
|
||||
[certTypes.dataVis]: 'certificate/data-vis.jade',
|
||||
[certTypes.backEnd]: 'certificate/back-end.jade',
|
||||
[certTypes.fullStack]: 'certificate/full-stack.jade'
|
||||
[certTypes.fullStack]: 'certificate/full-stack.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.dataVis]: 'certificate/data-visualization.jade',
|
||||
[certTypes.apisMicroservices]: 'certificate/apis-and-microservices.jade',
|
||||
[certTypes.infosecQa]:
|
||||
'certificate/information-security-and-quality-assurance.jade'
|
||||
};
|
||||
|
||||
const certText = {
|
||||
[certTypes.frontEnd]: 'Front End certified',
|
||||
[certTypes.dataVis]: 'Data Vis Certified',
|
||||
[certTypes.backEnd]: 'Back End Certified',
|
||||
[certTypes.fullStack]: 'Full Stack Certified'
|
||||
[certTypes.fullStack]: 'Full Stack Certified',
|
||||
[certTypes.respWebDesign]: 'Responsive Web Design Certified',
|
||||
[certTypes.frontEndLibs]: 'Front End Libraries Certified',
|
||||
[certTypes.jsAlgoDataStruct]:
|
||||
'JavaScript Algorithms and Data Structures Certified',
|
||||
[certTypes.dataVis]: 'Data Visualization Certified',
|
||||
[certTypes.apisMicroservices]: 'APIs and Microservices Certified',
|
||||
[certTypes.infosecQa]: 'Information Security and Quality Assurance Certified'
|
||||
};
|
||||
|
||||
const dateFormat = 'MMM DD, YYYY';
|
||||
@@ -139,7 +161,7 @@ function buildDisplayChallenges(
|
||||
module.exports = function(app) {
|
||||
const router = app.loopback.Router();
|
||||
const api = app.loopback.Router();
|
||||
const { AccessToken, Email, User } = app.models;
|
||||
const { Email, User } = app.models;
|
||||
const map$ = cachedMap(app.models);
|
||||
|
||||
function findUserByUsername$(username, fields) {
|
||||
@@ -153,23 +175,6 @@ module.exports = function(app) {
|
||||
);
|
||||
}
|
||||
|
||||
AccessToken.findOne$ = Observable.fromNodeCallback(
|
||||
AccessToken.findOne, AccessToken
|
||||
);
|
||||
|
||||
router.get('/login', function(req, res) {
|
||||
res.redirect(301, '/signin');
|
||||
});
|
||||
router.get('/logout', function(req, res) {
|
||||
res.redirect(301, '/signout');
|
||||
});
|
||||
router.get('/signup', getEmailSignin);
|
||||
router.get('/signin', getEmailSignin);
|
||||
router.get('/signout', signout);
|
||||
router.get('/email-signin', getEmailSignin);
|
||||
router.get('/deprecated-signin', getDepSignin);
|
||||
router.get('/passwordless-auth', invalidateAuthToken, getPasswordlessAuth);
|
||||
api.post('/passwordless-auth', postPasswordlessAuth);
|
||||
router.get(
|
||||
'/delete-my-account',
|
||||
sendNonUserToMap,
|
||||
@@ -208,11 +213,6 @@ module.exports = function(app) {
|
||||
showCert.bind(null, certTypes.frontEnd)
|
||||
);
|
||||
|
||||
api.get(
|
||||
'/:username/data-visualization-certification',
|
||||
showCert.bind(null, certTypes.dataVis)
|
||||
);
|
||||
|
||||
api.get(
|
||||
'/:username/back-end-certification',
|
||||
showCert.bind(null, certTypes.backEnd)
|
||||
@@ -223,6 +223,36 @@ module.exports = function(app) {
|
||||
(req, res) => res.redirect(req.url.replace('full-stack', 'back-end'))
|
||||
);
|
||||
|
||||
api.get(
|
||||
'/:username/responsive-web-design-certification',
|
||||
showCert.bind(null, certTypes.respWebDesign)
|
||||
);
|
||||
|
||||
api.get(
|
||||
'/:username/front-end-libraries-certification',
|
||||
showCert.bind(null, certTypes.frontEndLibs)
|
||||
);
|
||||
|
||||
api.get(
|
||||
'/:username/javascript-algorithms-data-structures-certification',
|
||||
showCert.bind(null, certTypes.jsAlgoDataStruct)
|
||||
);
|
||||
|
||||
api.get(
|
||||
'/:username/data-visualization-certification',
|
||||
showCert.bind(null, certTypes.dataVis)
|
||||
);
|
||||
|
||||
api.get(
|
||||
'/:username/apis-microservices-certification',
|
||||
showCert.bind(null, certTypes.apisMicroservices)
|
||||
);
|
||||
|
||||
api.get(
|
||||
'/:username/information-security-quality-assurance-certification',
|
||||
showCert.bind(null, certTypes.infosecQa)
|
||||
);
|
||||
|
||||
router.get('/:username', showUserProfile);
|
||||
router.get(
|
||||
'/:username/report-user/',
|
||||
@@ -240,179 +270,6 @@ module.exports = function(app) {
|
||||
app.use('/:lang', router);
|
||||
app.use(api);
|
||||
|
||||
const defaultErrorMsg = [ 'Oops, something is not right, please request a ',
|
||||
'fresh link to sign in / sign up.' ].join('');
|
||||
|
||||
function postPasswordlessAuth(req, res) {
|
||||
if (req.user || !(req.body && req.body.email)) {
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
return User.requestAuthEmail(req.body.email)
|
||||
.then(msg => {
|
||||
return res.status(200).send({ message: msg });
|
||||
})
|
||||
.catch(err => {
|
||||
debug(err);
|
||||
return res.status(200).send({ message: defaultErrorMsg });
|
||||
});
|
||||
}
|
||||
|
||||
function invalidateAuthToken(req, res, next) {
|
||||
if (req.user) {
|
||||
res.redirect('/');
|
||||
}
|
||||
|
||||
if (!req.query || !req.query.email || !req.query.token) {
|
||||
req.flash('info', { msg: defaultErrorMsg });
|
||||
return res.redirect('/email-signin');
|
||||
}
|
||||
|
||||
const authTokenId = req.query.token;
|
||||
const authEmailId = new Buffer(req.query.email, 'base64').toString();
|
||||
|
||||
return AccessToken.findOne$({ where: {id: authTokenId} })
|
||||
.map(authToken => {
|
||||
if (!authToken) {
|
||||
req.flash('info', { msg: defaultErrorMsg });
|
||||
return res.redirect('/email-signin');
|
||||
}
|
||||
|
||||
const userId = authToken.userId;
|
||||
return User.findById(userId, (err, user) => {
|
||||
if (err || !user || user.email !== authEmailId) {
|
||||
debug(err);
|
||||
req.flash('info', { msg: defaultErrorMsg });
|
||||
return res.redirect('/email-signin');
|
||||
}
|
||||
return authToken.validate((err, isValid) => {
|
||||
if (err) { throw err; }
|
||||
if (!isValid) {
|
||||
req.flash('info', { msg: [ 'Looks like the link you clicked has',
|
||||
'expired, please request a fresh link, to sign in.'].join('')
|
||||
});
|
||||
return res.redirect('/email-signin');
|
||||
}
|
||||
return authToken.destroy((err) => {
|
||||
if (err) { debug(err); }
|
||||
next();
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
.subscribe(
|
||||
() => {},
|
||||
next
|
||||
);
|
||||
}
|
||||
|
||||
function getPasswordlessAuth(req, res, next) {
|
||||
if (req.user) {
|
||||
req.flash('info', {
|
||||
msg: 'Hey, looks like you’re already signed in.'
|
||||
});
|
||||
return res.redirect('/');
|
||||
}
|
||||
|
||||
if (!req.query || !req.query.email || !req.query.token) {
|
||||
req.flash('info', { msg: defaultErrorMsg });
|
||||
return res.redirect('/email-signin');
|
||||
}
|
||||
|
||||
const email = new Buffer(req.query.email, 'base64').toString();
|
||||
|
||||
return User.findOne$({ where: { email }})
|
||||
.map(user => {
|
||||
|
||||
if (!user) {
|
||||
debug(`did not find a valid user with email: ${email}`);
|
||||
req.flash('info', { msg: defaultErrorMsg });
|
||||
return res.redirect('/email-signin');
|
||||
}
|
||||
|
||||
const emailVerified = true;
|
||||
const emailAuthLinkTTL = null;
|
||||
const emailVerifyTTL = null;
|
||||
user.update$({
|
||||
emailVerified, emailAuthLinkTTL, emailVerifyTTL
|
||||
})
|
||||
.do((user) => {
|
||||
user.emailVerified = emailVerified;
|
||||
user.emailAuthLinkTTL = emailAuthLinkTTL;
|
||||
user.emailVerifyTTL = emailVerifyTTL;
|
||||
});
|
||||
|
||||
return user.createAccessToken(
|
||||
{ ttl: User.settings.ttl }, (err, accessToken) => {
|
||||
if (err) { throw err; }
|
||||
|
||||
var config = {
|
||||
signed: !!req.signedCookies,
|
||||
maxAge: accessToken.ttl
|
||||
};
|
||||
|
||||
if (accessToken && accessToken.id) {
|
||||
debug('setting cookies');
|
||||
res.cookie('access_token', accessToken.id, config);
|
||||
res.cookie('userId', accessToken.userId, config);
|
||||
}
|
||||
|
||||
return req.logIn({
|
||||
id: accessToken.userId.toString() }, err => {
|
||||
if (err) { return next(err); }
|
||||
|
||||
debug('user logged in');
|
||||
|
||||
if (req.session && req.session.returnTo) {
|
||||
var redirectTo = req.session.returnTo;
|
||||
if (redirectTo === '/map-aside') {
|
||||
redirectTo = '/map';
|
||||
}
|
||||
return res.redirect(redirectTo);
|
||||
}
|
||||
|
||||
req.flash('success', { msg:
|
||||
'Success! You have signed in to your account. Happy Coding!'
|
||||
});
|
||||
return res.redirect('/');
|
||||
});
|
||||
});
|
||||
})
|
||||
.subscribe(
|
||||
() => {},
|
||||
next
|
||||
);
|
||||
}
|
||||
|
||||
function signout(req, res) {
|
||||
req.logout();
|
||||
res.redirect('/');
|
||||
}
|
||||
|
||||
|
||||
function getDepSignin(req, res) {
|
||||
if (req.user) {
|
||||
return res.redirect('/');
|
||||
}
|
||||
return res.render('account/deprecated-signin', {
|
||||
title: 'Sign in to freeCodeCamp using a Deprecated Login'
|
||||
});
|
||||
}
|
||||
|
||||
function getEmailSignin(req, res) {
|
||||
if (req.user) {
|
||||
return res.redirect('/');
|
||||
}
|
||||
if (isSignUpDisabled) {
|
||||
return res.render('account/beta', {
|
||||
title: 'New sign ups are disabled'
|
||||
});
|
||||
}
|
||||
return res.render('account/email-signin', {
|
||||
title: 'Sign in to freeCodeCamp using your Email Address'
|
||||
});
|
||||
}
|
||||
|
||||
function getAccount(req, res) {
|
||||
const { username } = req.user;
|
||||
return res.redirect('/' + username);
|
||||
@@ -586,9 +443,14 @@ module.exports = function(app) {
|
||||
isLocked: true,
|
||||
isAvailableForHire: true,
|
||||
isFrontEndCert: true,
|
||||
isDataVisCert: true,
|
||||
isBackEndCert: true,
|
||||
isFullStackCert: true,
|
||||
isRespWebDesignCert: true,
|
||||
isFrontEndLibsCert: true,
|
||||
isJsAlgoDataStructCert: true,
|
||||
isDataVisCert: true,
|
||||
isApisMicroservicesCert: true,
|
||||
isInfosecQaCert: true,
|
||||
isHonest: true,
|
||||
username: true,
|
||||
name: true,
|
||||
|
@@ -7,8 +7,7 @@
|
||||
"rest": {
|
||||
"handleErrors": false,
|
||||
"normalizeHttpPath": false,
|
||||
"xml": false,
|
||||
"handleErrors": false
|
||||
"xml": false
|
||||
},
|
||||
"json": {
|
||||
"strict": false,
|
||||
|
@@ -56,7 +56,8 @@
|
||||
"./middlewares/jade-helpers": {},
|
||||
"./middlewares/migrate-completed-challenges": {},
|
||||
"./middlewares/add-lang": {},
|
||||
"./middlewares/flash-cheaters": {}
|
||||
"./middlewares/flash-cheaters": {},
|
||||
"./middlewares/passport-login": {}
|
||||
},
|
||||
"files": {},
|
||||
"final:after": {
|
||||
@@ -64,9 +65,9 @@
|
||||
"./middlewares/error-handlers": {},
|
||||
"strong-error-handler": {
|
||||
"params": {
|
||||
"debug": false,
|
||||
"log": true
|
||||
}
|
||||
"debug": false,
|
||||
"log": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,33 +1,83 @@
|
||||
import errorHandler from 'errorhandler';
|
||||
import { inspect } from 'util';
|
||||
import _ from 'lodash/fp';
|
||||
import accepts from 'accepts';
|
||||
import { unwrapHandledError } from '../utils/create-handled-error.js';
|
||||
|
||||
export default function prodErrorHandler() {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
return errorHandler({ log: true });
|
||||
const isDev = process.env.NODE_ENV !== 'production';
|
||||
|
||||
const toString = Object.prototype.toString;
|
||||
// is full error or just trace
|
||||
// _.toString(new Error('foo')) => "Error: foo
|
||||
// Object.prototype.toString.call(new Error('foo')) => "[object Error]"
|
||||
const isInspect = val => !val.stack && _.toString(val) === toString.call(val);
|
||||
const stringifyErr = val => {
|
||||
if (val.stack) {
|
||||
return String(val.stack);
|
||||
}
|
||||
|
||||
const str = String(val);
|
||||
|
||||
return isInspect(val) ?
|
||||
inspect(val) :
|
||||
str;
|
||||
};
|
||||
|
||||
const createStackHtml = _.flow(
|
||||
_.cond([
|
||||
[isInspect, err => [err]],
|
||||
// may be stack or just err.msg
|
||||
[_.stubTrue, _.flow(stringifyErr, _.split('\n'), _.tail) ]
|
||||
]),
|
||||
_.map(_.escape),
|
||||
_.map(line => `<li>${line}</lin>`),
|
||||
_.join('')
|
||||
);
|
||||
|
||||
const createErrorTitle = _.cond([
|
||||
[
|
||||
_.negate(isInspect),
|
||||
_.flow(stringifyErr, _.split('\n'), _.head, _.defaultTo('Error'))
|
||||
],
|
||||
[_.stubTrue, _.constant('Error')]
|
||||
]);
|
||||
|
||||
export default function prodErrorHandler() {
|
||||
// error handling in production.
|
||||
// disabling eslint due to express parity rules for error handlers
|
||||
return function(err, req, res, next) { // eslint-disable-line
|
||||
// respect err.status
|
||||
if (err.status) {
|
||||
res.statusCode = err.status;
|
||||
}
|
||||
|
||||
// default status code to 500
|
||||
if (res.statusCode < 400) {
|
||||
res.statusCode = 500;
|
||||
const handled = unwrapHandledError(err);
|
||||
// respect handled error status
|
||||
let status = handled.status || err.status || res.statusCode;
|
||||
if (!handled.status && status < 400) {
|
||||
status = 500;
|
||||
}
|
||||
res.status(status);
|
||||
|
||||
// parse res type
|
||||
const accept = accepts(req);
|
||||
const type = accept.type('html', 'json', 'text');
|
||||
const handled = unwrapHandledError(err);
|
||||
|
||||
const redirectTo = handled.redirectTo || '/map';
|
||||
const redirectTo = handled.redirectTo || '/';
|
||||
const message = handled.message ||
|
||||
'Oops! Something went wrong. Please try again later';
|
||||
|
||||
if (isDev) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
if (type === 'html') {
|
||||
if (isDev) {
|
||||
return res.render(
|
||||
'dev-error',
|
||||
{
|
||||
...handled,
|
||||
stack: createStackHtml(err),
|
||||
errorTitle: createErrorTitle(err),
|
||||
title: 'freeCodeCamp - Server Error',
|
||||
status
|
||||
}
|
||||
);
|
||||
}
|
||||
if (typeof req.flash === 'function') {
|
||||
req.flash(
|
||||
handled.type || 'danger',
|
||||
@@ -39,6 +89,7 @@ export default function prodErrorHandler() {
|
||||
} else if (type === 'json') {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
return res.send({
|
||||
type: handled.type || 'errors',
|
||||
message
|
||||
});
|
||||
// plain text
|
||||
|
@@ -8,7 +8,7 @@ import {
|
||||
|
||||
const log = debug('fcc:middlewares:error-reporter');
|
||||
|
||||
export default function keymetrics() {
|
||||
export default function errorHandler() {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
return (err, req, res, next) => {
|
||||
if (isHandledError(err)) {
|
||||
|
21
server/middlewares/passport-login.js
Normal file
21
server/middlewares/passport-login.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import _ from 'lodash';
|
||||
import { Observable } from 'rx';
|
||||
import { login } from 'passport/lib/http/request';
|
||||
|
||||
// make login polymorphic
|
||||
// if supplied callback it works as normal
|
||||
// if called without callback it returns an observable
|
||||
// login(user, options?, cb?) => Void|Observable
|
||||
function login$(...args) {
|
||||
console.log('args');
|
||||
if (_.isFunction(_.last(args))) {
|
||||
return login.apply(this, args);
|
||||
}
|
||||
return Observable.fromNodeCallback(login).apply(this, args);
|
||||
}
|
||||
export default function passportLogin() {
|
||||
return (req, res, next) => {
|
||||
req.login = req.logIn = login$;
|
||||
next();
|
||||
};
|
||||
}
|
@@ -30,25 +30,27 @@ export default function() {
|
||||
customSanitizers: {
|
||||
// Refer : http://stackoverflow.com/a/430240/1932901
|
||||
trimTags(value) {
|
||||
const tagBody = '(?:[^"\'>]|"[^"]*"|\'[^\']*\')*';
|
||||
const tagOrComment = new RegExp(
|
||||
'<(?:'
|
||||
// Comment body.
|
||||
+ '!--(?:(?:-*[^->])*--+|-?)'
|
||||
// Special "raw text" elements whose content should be elided.
|
||||
+ '|script\\b' + tagBody + '>[\\s\\S]*?</script\\s*'
|
||||
+ '|style\\b' + tagBody + '>[\\s\\S]*?</style\\s*'
|
||||
// Regular name
|
||||
+ '|/?[a-z]'
|
||||
+ tagBody
|
||||
+ ')>',
|
||||
'gi');
|
||||
let rawValue;
|
||||
do {
|
||||
rawValue = value;
|
||||
value = value.replace(tagOrComment, '');
|
||||
} while (value !== rawValue);
|
||||
return value.replace(/</g, '<');
|
||||
const tagBody = '(?:[^"\'>]|"[^"]*"|\'[^\']*\')*';
|
||||
const tagOrComment = new RegExp(
|
||||
'<(?:'
|
||||
// Comment body.
|
||||
+ '!--(?:(?:-*[^->])*--+|-?)'
|
||||
// Special "raw text" elements whose content should be elided.
|
||||
+ '|script\\b' + tagBody + '>[\\s\\S]*?</script\\s*'
|
||||
+ '|style\\b' + tagBody + '>[\\s\\S]*?</style\\s*'
|
||||
// Regular name
|
||||
+ '|/?[a-z]'
|
||||
+ tagBody
|
||||
+ ')>',
|
||||
'gi'
|
||||
);
|
||||
let rawValue;
|
||||
do {
|
||||
rawValue = value;
|
||||
value = value.replace(tagOrComment, '');
|
||||
} while (value !== rawValue);
|
||||
|
||||
return value.replace(/</g, '<');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -78,5 +78,9 @@
|
||||
"about": {
|
||||
"dataSource": "db",
|
||||
"public": true
|
||||
},
|
||||
"AuthToken": {
|
||||
"dataSource": "db",
|
||||
"public": false
|
||||
}
|
||||
}
|
||||
|
15
server/models/auth-token.js
Normal file
15
server/models/auth-token.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Observable } from 'rx';
|
||||
|
||||
export default function(AuthToken) {
|
||||
AuthToken.on('dataSourceAttached', () => {
|
||||
AuthToken.findOne$ = Observable.fromNodeCallback(
|
||||
AuthToken.findOne.bind(AuthToken)
|
||||
);
|
||||
AuthToken.prototype.validate = Observable.fromNodeCallback(
|
||||
AuthToken.prototype.validate
|
||||
);
|
||||
AuthToken.prototype.destroy = Observable.fromNodeCallback(
|
||||
AuthToken.prototype.destroy
|
||||
);
|
||||
});
|
||||
}
|
13
server/models/auth-token.json
Normal file
13
server/models/auth-token.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "AuthToken",
|
||||
"base": "AccessToken",
|
||||
"idInjection": true,
|
||||
"options": {
|
||||
"validateUpsert": true
|
||||
},
|
||||
"properties": {},
|
||||
"validations": [],
|
||||
"relations": {},
|
||||
"acls": [],
|
||||
"methods": {}
|
||||
}
|
@@ -1,32 +1,33 @@
|
||||
// this ensures node understands the future
|
||||
require('babel-register');
|
||||
const _ = require('lodash');
|
||||
const createDebugger = require('debug');
|
||||
|
||||
var startTime = Date.now();
|
||||
var timeoutHandler;
|
||||
const log = createDebugger('fcc:server:production-start');
|
||||
const startTime = Date.now();
|
||||
// force logger to always output
|
||||
// this may be brittle
|
||||
log.enabled = true;
|
||||
// this is where server starts booting up
|
||||
var app = require('./server');
|
||||
|
||||
console.log('waiting for db to connect');
|
||||
const app = require('./server');
|
||||
|
||||
|
||||
var onConnect = function() {
|
||||
console.log('db connected in %s ms', Date.now() - startTime);
|
||||
let timeoutHandler;
|
||||
let killTime = 15;
|
||||
|
||||
const onConnect = _.once(() => {
|
||||
log('db connected in: %s', Date.now() - startTime);
|
||||
if (timeoutHandler) {
|
||||
clearTimeout(timeoutHandler);
|
||||
}
|
||||
app.start();
|
||||
};
|
||||
});
|
||||
|
||||
timeoutHandler = setTimeout(function() {
|
||||
var message =
|
||||
'db did not connect after ' +
|
||||
(Date.now() - startTime) +
|
||||
' ms --- crashing hard';
|
||||
|
||||
console.log(message);
|
||||
timeoutHandler = setTimeout(() => {
|
||||
const message = `db did not connect after ${killTime}s -- crashing hard`;
|
||||
// purposely shutdown server
|
||||
// pm2 should restart this in production
|
||||
throw new Error(message);
|
||||
}, 15000);
|
||||
}, killTime * 1000);
|
||||
|
||||
app.dataSources.db.on('connected', onConnect);
|
||||
|
@@ -1,4 +1,5 @@
|
||||
require('dotenv').load();
|
||||
require('./utils/webpack-code-split-polyfill');
|
||||
|
||||
if (process.env.OPBEAT_ID) {
|
||||
console.log('loading opbeat');
|
||||
@@ -9,33 +10,23 @@ if (process.env.OPBEAT_ID) {
|
||||
});
|
||||
}
|
||||
|
||||
var _ = require('lodash'),
|
||||
Rx = require('rx'),
|
||||
loopback = require('loopback'),
|
||||
boot = require('loopback-boot'),
|
||||
expressState = require('express-state'),
|
||||
path = require('path'),
|
||||
setupPassport = require('./component-passport');
|
||||
const _ = require('lodash');
|
||||
const Rx = require('rx');
|
||||
const loopback = require('loopback');
|
||||
const boot = require('loopback-boot');
|
||||
const expressState = require('express-state');
|
||||
const path = require('path');
|
||||
const setupPassport = require('./component-passport');
|
||||
const createDebugger = require('debug');
|
||||
|
||||
const log = createDebugger('fcc:server');
|
||||
// force logger to always output
|
||||
// this may be brittle
|
||||
log.enabled = true;
|
||||
|
||||
// polyfill for webpack bundle splitting
|
||||
const requireProto = Object.getPrototypeOf(require);
|
||||
if (!requireProto.hasOwnProperty('ensure')) {
|
||||
Object.defineProperties(
|
||||
requireProto,
|
||||
{
|
||||
ensure: {
|
||||
value: function ensure(modules, callback) {
|
||||
callback(this);
|
||||
},
|
||||
writable: false,
|
||||
enumerable: false
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
Rx.config.longStackSupport = process.env.NODE_DEBUG !== 'production';
|
||||
var app = loopback();
|
||||
var isBeta = !!process.env.BETA;
|
||||
const app = loopback();
|
||||
const isBeta = !!process.env.BETA;
|
||||
|
||||
expressState.extend(app);
|
||||
app.set('state namespace', '__fcc__');
|
||||
@@ -52,17 +43,36 @@ boot(app, {
|
||||
|
||||
setupPassport(app);
|
||||
|
||||
const { db } = app.datasources;
|
||||
db.on('connected', _.once(() => log('db connected')));
|
||||
app.start = _.once(function() {
|
||||
app.listen(app.get('port'), function() {
|
||||
const server = app.listen(app.get('port'), function() {
|
||||
app.emit('started');
|
||||
console.log(
|
||||
log(
|
||||
'freeCodeCamp server listening on port %d in %s',
|
||||
app.get('port'),
|
||||
app.get('env')
|
||||
);
|
||||
if (isBeta) {
|
||||
console.log('freeCodeCamp is in beta mode');
|
||||
log('freeCodeCamp is in beta mode');
|
||||
}
|
||||
log(`connecting to db at ${db.settings.url}`);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
log('Shutting down server');
|
||||
server.close(() => {
|
||||
log('Server is closed');
|
||||
});
|
||||
log('closing db connection');
|
||||
db.disconnect()
|
||||
.then(() => {
|
||||
log('DB connection closed');
|
||||
// exit process
|
||||
// this may close kept alive sockets
|
||||
// eslint-disable-next-line no-process-exit
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -21,6 +21,11 @@ const publicUserProps = [
|
||||
'isBackEndCert',
|
||||
'isDataVisCert',
|
||||
'isFullStackCert',
|
||||
'isRespWebDesignCert',
|
||||
'isFrontEndLibsCert',
|
||||
'isJsAlgoDataStructCert',
|
||||
'isApisMicroservicesCert',
|
||||
'isInfosecQaCert',
|
||||
|
||||
'githubURL',
|
||||
'sendMonthlyEmail',
|
||||
|
@@ -1,6 +1,11 @@
|
||||
{
|
||||
"frontEnd": "isFrontEndCert",
|
||||
"backEnd": "isBackEndCert",
|
||||
"fullStack": "isFullStackCert",
|
||||
"respWebDesign": "isRespWebDesignCert",
|
||||
"frontEndLibs": "isFrontEndLibsCert",
|
||||
"jsAlgoDataStruct": "isJsAlgoDataStructCert",
|
||||
"dataVis": "isDataVisCert",
|
||||
"fullStack": "isFullStackCert"
|
||||
"apisMicroservices": "isApisMicroservicesCert",
|
||||
"infosecQa": "isInfosecQaCert"
|
||||
}
|
||||
|
@@ -1,6 +1,11 @@
|
||||
{
|
||||
"frontEndCert": "Front End Development Certification",
|
||||
"backEndCert": "Back End Development Certification",
|
||||
"dataVisCert": "Data Visualisation Certification",
|
||||
"fullStackCert": "Full Stack Development Certification"
|
||||
"fullStackCert": "Full Stack Development Certification",
|
||||
"respWebDesign": "Responsive Web Design Certification",
|
||||
"frontEndLibs": "Front End Libraries Certification",
|
||||
"jsAlgoDataStruct": "JavaScript Algorithms and Data Structures Certification",
|
||||
"dataVis": "Data Visualisation Certification",
|
||||
"apisMicroservices": "APIs and Microservices Certification",
|
||||
"infosecQa": "Information Security and Quality Assurance Certification"
|
||||
}
|
||||
|
@@ -10,9 +10,14 @@ export { commitGoals };
|
||||
export function completeCommitment$(user) {
|
||||
const {
|
||||
isFrontEndCert,
|
||||
isDataVisCert,
|
||||
isBackEndCert,
|
||||
isFullStackCert
|
||||
isFullStackCert,
|
||||
isRespWebDesignCert,
|
||||
isFrontEndLibsCert,
|
||||
isJsAlgoDataStructCert,
|
||||
isDataVisCert,
|
||||
isApisMicroservicesCert,
|
||||
isInfosecQaCert
|
||||
} = user;
|
||||
|
||||
return Observable.fromNodeCallback(user.pledge, user)()
|
||||
@@ -25,9 +30,15 @@ export function completeCommitment$(user) {
|
||||
|
||||
if (
|
||||
(isFrontEndCert && goal === commitGoals.frontEndCert) ||
|
||||
(isDataVisCert && goal === commitGoals.dataVisCert) ||
|
||||
(isBackEndCert && goal === commitGoals.backEndCert) ||
|
||||
(isFullStackCert && goal === commitGoals.fullStackCert)
|
||||
(isFullStackCert && goal === commitGoals.fullStackCert) ||
|
||||
(isRespWebDesignCert && goal === commitGoals.respWebDesignCert) ||
|
||||
(isFrontEndLibsCert && goal === commitGoals.frontEndLibsCert) ||
|
||||
(isJsAlgoDataStructCert && goal === commitGoals.jsAlgoDataStructCert) ||
|
||||
(isDataVisCert && goal === commitGoals.dataVisCert) ||
|
||||
(isApisMicroservicesCert &&
|
||||
goal === commitGoals.apisMicroservicesCert) ||
|
||||
(isInfosecQaCert && goal === commitGoals.infosecQaCert)
|
||||
) {
|
||||
debug('marking goal complete');
|
||||
pledge.isCompleted = true;
|
||||
|
@@ -1,6 +1,11 @@
|
||||
{
|
||||
"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",
|
||||
"frontEndChallengeId": "561add10cb82ac38a17513be",
|
||||
"dataVisChallengeId": "561add10cb82ac38a17513b3",
|
||||
"backEndChallengeId": "660add10cb82ac38a17513be"
|
||||
"backEndChallengeId": "660add10cb82ac38a17513be",
|
||||
"respWebDesignId": "561add10cb82ac38a17513bc",
|
||||
"frontEndLibsId": "561acd10cb82ac38a17513bc",
|
||||
"jsAlgoDataStructId": "561abd10cb81ac38a17513bc",
|
||||
"dataVisId": "561add10cb82ac39a17513bc",
|
||||
"apisMicroservicesId": "561add10cb82ac38a17523bc",
|
||||
"infosecQaId": "561add10cb82ac38a17213bc"
|
||||
}
|
||||
|
@@ -11,8 +11,20 @@ export function unwrapHandledError(err) {
|
||||
export function wrapHandledError(err, {
|
||||
type,
|
||||
message,
|
||||
redirectTo
|
||||
redirectTo,
|
||||
status = 200
|
||||
}) {
|
||||
err[_handledError] = { type, message, redirectTo };
|
||||
err[_handledError] = { type, message, redirectTo, status };
|
||||
return err;
|
||||
}
|
||||
|
||||
export const createValidatorErrorFormatter = (type, redirectTo, status) =>
|
||||
({ msg }) => wrapHandledError(
|
||||
new Error(msg),
|
||||
{
|
||||
type,
|
||||
message: msg,
|
||||
redirectTo,
|
||||
status
|
||||
}
|
||||
);
|
||||
|
@@ -209,6 +209,7 @@ export function getChallenge(
|
||||
) {
|
||||
return map
|
||||
.flatMap(({ entities, result: { superBlocks } }) => {
|
||||
const superBlock = entities.superBlock;
|
||||
const block = entities.block[blockDashedName];
|
||||
const challenge = entities.challenge[challengeDashedName];
|
||||
return Observable.if(
|
||||
@@ -226,6 +227,7 @@ export function getChallenge(
|
||||
`/challenges/${block.dashedName}/${challenge.dashedName}` :
|
||||
false,
|
||||
entities: {
|
||||
superBlock,
|
||||
challenge: {
|
||||
[challenge.dashedName]: mapChallengeToLang(challenge, lang)
|
||||
}
|
||||
|
@@ -43,3 +43,13 @@ export function ifNotVerifiedRedirectToSettings(req, res, next) {
|
||||
}
|
||||
return next();
|
||||
}
|
||||
|
||||
export function ifUserRedirectTo(path = '/', status) {
|
||||
status = status === 302 ? 302 : 301;
|
||||
return (req, res, next) => {
|
||||
if (req.user) {
|
||||
return res.status(status).redirect(path);
|
||||
}
|
||||
return next();
|
||||
};
|
||||
}
|
||||
|
18
server/utils/webpack-code-split-polyfill.js
Normal file
18
server/utils/webpack-code-split-polyfill.js
Normal file
@@ -0,0 +1,18 @@
|
||||
export default function codeSplitPolyfill() {
|
||||
// polyfill for webpack bundle splitting
|
||||
const requireProto = Object.getPrototypeOf(require);
|
||||
if (!requireProto.hasOwnProperty('ensure')) {
|
||||
Object.defineProperties(
|
||||
requireProto,
|
||||
{
|
||||
ensure: {
|
||||
value: function ensure(modules, callback) {
|
||||
callback(this);
|
||||
},
|
||||
writable: false,
|
||||
enumerable: false
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@@ -34,18 +34,17 @@ block content
|
||||
a(href="/deprecated-signin") Or click here if you want to sign in with other options.
|
||||
|
||||
script.
|
||||
$(document).ready(function() {
|
||||
|
||||
function disableMagicButton (isDisabled) {
|
||||
if (isDisabled) {
|
||||
$('#magic-btn')
|
||||
.html('<span class="fa fa-circle-o-notch fa-spin fa-fw"></span>')
|
||||
.prop('disabled', true);
|
||||
} else {
|
||||
$('#magic-btn')
|
||||
.html('<span class="fa.fa-envelope">Get a magic link to sign in.</span>')
|
||||
.prop('disabled', false);
|
||||
}
|
||||
$(document).ready(function() {
|
||||
function disableMagicButton (isDisabled) {
|
||||
if (isDisabled) {
|
||||
$('#magic-btn')
|
||||
.prop('disabled', true)
|
||||
.html('<span style="color:#E0E0E0;"><i class="fa fa-circle-o-notch fa-spin fa-fw"></i>Ok - We will attempt sending to the email above.</span>');
|
||||
} else {
|
||||
$('#magic-btn')
|
||||
.prop('disabled', true)
|
||||
.html('<span style="color:#E0E0E0;">Did not get a link? Reload the page and resend again.</span>');
|
||||
}
|
||||
}
|
||||
|
||||
$('form').submit(function(event){
|
||||
@@ -54,34 +53,53 @@ block content
|
||||
disableMagicButton(true);
|
||||
var $form = $(event.target);
|
||||
$.ajax({
|
||||
type : 'POST',
|
||||
url : $form.attr('action'),
|
||||
data : $form.serialize(),
|
||||
dataType : 'json',
|
||||
encode : true,
|
||||
xhrFields : { withCredentials: true }
|
||||
type : 'POST',
|
||||
url : $form.attr('action'),
|
||||
data : $form.serialize(),
|
||||
dataType : 'json',
|
||||
encode : true,
|
||||
xhrFields : { withCredentials: true }
|
||||
})
|
||||
.fail(error => {
|
||||
if (error.responseText){
|
||||
var data = JSON.parse(error.responseText);
|
||||
if(data.error && data.error.message)
|
||||
var data = JSON.parse(error.responseText);
|
||||
if(data.error && data.error.message) {
|
||||
$('#flash-content').html(data.error.message);
|
||||
$('#flash-board')
|
||||
.removeClass('alert-success')
|
||||
.addClass('alert-info')
|
||||
.slideDown(400)
|
||||
.delay(800)
|
||||
.fadeIn();
|
||||
disableMagicButton(false);
|
||||
}
|
||||
}
|
||||
})
|
||||
.done(data =>{
|
||||
if(data && data.message){
|
||||
.done(data => {
|
||||
if(data && data.message) {
|
||||
var alertType = 'alert-';
|
||||
switch (data.type) {
|
||||
case 'errors': {
|
||||
alertType += 'danger';
|
||||
break
|
||||
}
|
||||
case 'success': {
|
||||
alertType += 'success';
|
||||
break
|
||||
}
|
||||
default: {
|
||||
alertType += 'info';
|
||||
}
|
||||
}
|
||||
$('#flash-content').html(data.message);
|
||||
$('#flash-board')
|
||||
.removeClass('alert-info')
|
||||
.addClass('alert-success')
|
||||
.fadeIn();
|
||||
.removeClass('alert-info alert-success alert-danger')
|
||||
.addClass(alertType)
|
||||
.slideDown(400)
|
||||
.delay(800)
|
||||
.fadeIn();
|
||||
disableMagicButton(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -9,10 +9,10 @@ block content
|
||||
if (!user.isGithubCool)
|
||||
a.btn.btn-lg.btn-block.btn-github.btn-link-social(href='/link/github')
|
||||
i.fa.fa-github
|
||||
| Link my GitHub to unlock my portfolio
|
||||
| Link my GitHub to enable my public profile
|
||||
.col-xs-12
|
||||
a.btn.btn-lg.btn-block.btn-primary.btn-link-social(href='/settings')
|
||||
| Update your settings
|
||||
| Update my settings
|
||||
.col-xs-12
|
||||
a.btn.btn-lg.btn-block.btn-primary.btn-link-social(href='/signout')
|
||||
| Sign me out of freeCodeCamp
|
||||
@@ -56,6 +56,21 @@ block content
|
||||
if isBackEndCert
|
||||
.button-spacer
|
||||
a.btn.btn-primary.btn-block(href='/' + username + '/back-end-certification') View My Back End Development Certification
|
||||
if isRespWebDesignCert
|
||||
.button-spacer
|
||||
a.btn.btn-primary.btn-block(href='/' + username + '/responsive-web-design-certification') View My Responsive Web Design Certification
|
||||
if isFrontEndLibsCert
|
||||
.button-spacer
|
||||
a.btn.btn-primary.btn-block(href='/' + username + '/front-end-libraries-certification') View My Front End Libraries Certification
|
||||
if isJsAlgoDataStructCert
|
||||
.button-spacer
|
||||
a.btn.btn-primary.btn-block(href='/' + username + '/javascript-algorithms-data-structures-certification') View My JavaScript Algorithms Data Structures Certification
|
||||
if isApisMicroservicesCert
|
||||
.button-spacer
|
||||
a.btn.btn-primary.btn-block(href='/' + username + '/apis-microservices-certification') View My APIs Microservices Certification
|
||||
if isInfosecQaCert
|
||||
.button-spacer
|
||||
a.btn.btn-primary.btn-block(href='/' + username + '/information-security-quality-assurance-certification') View My Information Sequrity Quality Assurance Certification
|
||||
if (user && user.username != username)
|
||||
.button-spacer
|
||||
a.btn.btn-primary.btn-block(href='/' + username + '/report-user/') Report this user's profile for abuse
|
||||
|
32
server/views/certificate/advanced-front-end.jade
Normal file
32
server/views/certificate/advanced-front-end.jade
Normal file
@@ -0,0 +1,32 @@
|
||||
meta(name='viewport', content='width=device-width, initial-scale=1')
|
||||
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
|
||||
include styles
|
||||
|
||||
.certificate-wrapper.container
|
||||
.row
|
||||
header
|
||||
.col-md-5.col-sm-12
|
||||
.logo
|
||||
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
|
||||
.col-md-7.col-sm-12
|
||||
.issue-date Issued
|
||||
strong #{date}
|
||||
|
||||
section.information
|
||||
.information-container
|
||||
h3 This certifies that
|
||||
h1
|
||||
strong= name
|
||||
h3 has successfully completed freeCodeCamp's
|
||||
h1
|
||||
strong Advanced Frontend Projects
|
||||
h4 1 of 3 legacy freeCodeCamp certificates, representing approximately 400 hours of coursework
|
||||
|
||||
footer
|
||||
.row.signatures
|
||||
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
|
||||
p
|
||||
strong Quincy Larson
|
||||
p Executive Director, freeCodeCamp.org
|
||||
.row
|
||||
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/advanced-front-end-certification
|
32
server/views/certificate/apis-and-microservices.jade
Normal file
32
server/views/certificate/apis-and-microservices.jade
Normal file
@@ -0,0 +1,32 @@
|
||||
meta(name='viewport', content='width=device-width, initial-scale=1')
|
||||
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
|
||||
include styles
|
||||
|
||||
.certificate-wrapper.container
|
||||
.row
|
||||
header
|
||||
.col-md-5.col-sm-12
|
||||
.logo
|
||||
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
|
||||
.col-md-7.col-sm-12
|
||||
.issue-date Issued
|
||||
strong #{date}
|
||||
|
||||
section.information
|
||||
.information-container
|
||||
h3 This certifies that
|
||||
h1
|
||||
strong= name
|
||||
h3 has successfully completed freeCodeCamp's
|
||||
h1
|
||||
strong APIs and Microservices Projects
|
||||
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
|
||||
|
||||
footer
|
||||
.row.signatures
|
||||
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
|
||||
p
|
||||
strong Quincy Larson
|
||||
p Executive Director, freeCodeCamp.org
|
||||
.row
|
||||
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/apis-and-microservices-certification
|
@@ -3,34 +3,30 @@ link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-boot
|
||||
include styles
|
||||
|
||||
.certificate-wrapper.container
|
||||
.row
|
||||
header
|
||||
.col-md-5.col-sm-12
|
||||
.logo
|
||||
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
|
||||
.col-md-7.col-sm-12
|
||||
.issue-date Issued
|
||||
strong #{date}
|
||||
|
||||
section.information
|
||||
.information-container
|
||||
h3 This certifies that
|
||||
h1
|
||||
strong= name
|
||||
h3 has successfully completed the
|
||||
h1
|
||||
strong Back End Development Projects
|
||||
h4 (400 hours of coursework & 1 of 3 freeCodeCamp certificates)
|
||||
|
||||
footer
|
||||
.row.signatures
|
||||
.col-md-6
|
||||
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
|
||||
p
|
||||
strong Quincy Larson
|
||||
.col-md-6
|
||||
img(class='img-responsive', src='https://i.imgur.com/b0YdXS4.png', alt="Michael D. Johnson's Signature")
|
||||
p
|
||||
strong Michael D. Johnson
|
||||
.row
|
||||
p.verify Verify this certificate at: https://www.freecodecamp.org/#{username}/back-end-certification
|
||||
.row
|
||||
header
|
||||
.col-md-5.col-sm-12
|
||||
.logo
|
||||
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
|
||||
.col-md-7.col-sm-12
|
||||
.issue-date Issued
|
||||
strong #{date}
|
||||
|
||||
section.information
|
||||
.information-container
|
||||
h3 This certifies that
|
||||
h1
|
||||
strong= name
|
||||
h3 has successfully completed freeCodeCamp's
|
||||
h1
|
||||
strong Back End Development Projects
|
||||
h4 1 of 3 legacy freeCodeCamp certificates, representing approximately 400 hours of coursework
|
||||
|
||||
footer
|
||||
.row.signatures
|
||||
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
|
||||
p
|
||||
strong Quincy Larson
|
||||
p Executive Director, freeCodeCamp.org
|
||||
.row
|
||||
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/back-end-certification
|
||||
|
@@ -1,36 +0,0 @@
|
||||
meta(name='viewport', content='width=device-width, initial-scale=1')
|
||||
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
|
||||
include styles
|
||||
|
||||
.certificate-wrapper.container
|
||||
.row
|
||||
header
|
||||
.col-md-5.col-sm-12
|
||||
.logo
|
||||
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
|
||||
.col-md-7.col-sm-12
|
||||
.issue-date Issued
|
||||
strong #{date}
|
||||
|
||||
section.information
|
||||
.information-container
|
||||
h3 This certifies that
|
||||
h1
|
||||
strong= name
|
||||
h3 has successfully completed the
|
||||
h1
|
||||
strong Data Visualization Projects
|
||||
h4 (400 hours of coursework & 1 of 3 freeCodeCamp certificates)
|
||||
|
||||
footer
|
||||
.row.signatures
|
||||
.col-md-6
|
||||
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
|
||||
p
|
||||
strong Quincy Larson
|
||||
.col-md-6
|
||||
img(class='img-responsive', src='https://i.imgur.com/b0YdXS4.png', alt="Michael D. Johnson's Signature")
|
||||
p
|
||||
strong Michael D. Johnson
|
||||
.row
|
||||
p.verify Verify this certificate at: https://www.freecodecamp.org/#{username}/data-visualization-certification
|
32
server/views/certificate/data-visualization.jade
Normal file
32
server/views/certificate/data-visualization.jade
Normal file
@@ -0,0 +1,32 @@
|
||||
meta(name='viewport', content='width=device-width, initial-scale=1')
|
||||
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
|
||||
include styles
|
||||
|
||||
.certificate-wrapper.container
|
||||
.row
|
||||
header
|
||||
.col-md-5.col-sm-12
|
||||
.logo
|
||||
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
|
||||
.col-md-7.col-sm-12
|
||||
.issue-date Issued
|
||||
strong #{date}
|
||||
|
||||
section.information
|
||||
.information-container
|
||||
h3 This certifies that
|
||||
h1
|
||||
strong= name
|
||||
h3 has successfully completed freeCodeCamp's
|
||||
h1
|
||||
strong Data Visualization Projects
|
||||
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
|
||||
|
||||
footer
|
||||
.row.signatures
|
||||
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
|
||||
p
|
||||
strong Quincy Larson
|
||||
p Executive Director, freeCodeCamp.org
|
||||
.row
|
||||
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/data-visualization-certification
|
32
server/views/certificate/front-end-libraries.jade
Normal file
32
server/views/certificate/front-end-libraries.jade
Normal file
@@ -0,0 +1,32 @@
|
||||
meta(name='viewport', content='width=device-width, initial-scale=1')
|
||||
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
|
||||
include styles
|
||||
|
||||
.certificate-wrapper.container
|
||||
.row
|
||||
header
|
||||
.col-md-5.col-sm-12
|
||||
.logo
|
||||
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
|
||||
.col-md-7.col-sm-12
|
||||
.issue-date Issued
|
||||
strong #{date}
|
||||
|
||||
section.information
|
||||
.information-container
|
||||
h3 This certifies that
|
||||
h1
|
||||
strong= name
|
||||
h3 has successfully completed freeCodeCamp's
|
||||
h1
|
||||
strong Front End Libraries Projects
|
||||
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
|
||||
|
||||
footer
|
||||
.row.signatures
|
||||
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
|
||||
p
|
||||
strong Quincy Larson
|
||||
p Executive Director, freeCodeCamp.org
|
||||
.row
|
||||
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/front-end-libraries-certification
|
@@ -3,34 +3,30 @@ link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-boot
|
||||
include styles
|
||||
|
||||
.certificate-wrapper.container
|
||||
.row
|
||||
header
|
||||
.col-md-5.col-sm-12
|
||||
.logo
|
||||
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
|
||||
.col-md-7.col-sm-12
|
||||
.issue-date Issued
|
||||
strong #{date}
|
||||
|
||||
section.information
|
||||
.information-container
|
||||
h3 This certifies that
|
||||
h1
|
||||
strong= name
|
||||
h3 has successfully completed the
|
||||
h1
|
||||
strong Front End Development Projects
|
||||
h4 (400 hours of coursework & 1 of 3 freeCodeCamp certificates)
|
||||
|
||||
footer
|
||||
.row.signatures
|
||||
.col-md-6
|
||||
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
|
||||
p
|
||||
strong Quincy Larson
|
||||
.col-md-6
|
||||
img(class='img-responsive', src='https://i.imgur.com/b0YdXS4.png', alt="Michael D. Johnson's Signature")
|
||||
p
|
||||
strong Michael D. Johnson
|
||||
.row
|
||||
p.verify Verify this certificate at: https://www.freecodecamp.org/#{username}/front-end-certification
|
||||
.row
|
||||
header
|
||||
.col-md-5.col-sm-12
|
||||
.logo
|
||||
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
|
||||
.col-md-7.col-sm-12
|
||||
.issue-date Issued
|
||||
strong #{date}
|
||||
|
||||
section.information
|
||||
.information-container
|
||||
h3 This certifies that
|
||||
h1
|
||||
strong= name
|
||||
h3 has successfully completed freeCodeCamp's
|
||||
h1
|
||||
strong Front End Development Projects
|
||||
h4 1 of 3 legacy freeCodeCamp certificates, representing approximately 400 hours of coursework
|
||||
|
||||
footer
|
||||
.row.signatures
|
||||
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
|
||||
p
|
||||
strong Quincy Larson
|
||||
p Executive Director, freeCodeCamp.org
|
||||
.row
|
||||
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/front-end-certification
|
||||
|
@@ -3,34 +3,30 @@ link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-boot
|
||||
include styles
|
||||
|
||||
.certificate-wrapper.container
|
||||
.row
|
||||
header
|
||||
.col-md-5.col-sm-12
|
||||
.logo
|
||||
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
|
||||
.col-md-7.col-sm-12
|
||||
.issue-date Issued
|
||||
strong #{date}
|
||||
|
||||
section.information
|
||||
.information-container
|
||||
h3 This certifies that
|
||||
h1
|
||||
strong= name
|
||||
h3 has successfully completed the
|
||||
h1
|
||||
strong Full Stack Development Projects
|
||||
h4 (400 hours of coursework & 1 of 3 freeCodeCamp certificates)
|
||||
|
||||
footer
|
||||
.row.signatures
|
||||
.col-md-6
|
||||
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
|
||||
p
|
||||
strong Quincy Larson
|
||||
.col-md-6
|
||||
img(class='img-responsive', src='https://i.imgur.com/b0YdXS4.png', alt="Michael D. Johnson's Signature")
|
||||
p
|
||||
strong Michael D. Johnson
|
||||
.row
|
||||
p.verify Verify this certificate at: https://www.freecodecamp.org/#{username}/full-stack-certification
|
||||
.row
|
||||
header
|
||||
.col-md-5.col-sm-12
|
||||
.logo
|
||||
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
|
||||
.col-md-7.col-sm-12
|
||||
.issue-date Issued
|
||||
strong #{date}
|
||||
|
||||
section.information
|
||||
.information-container
|
||||
h3 This certifies that
|
||||
h1
|
||||
strong= name
|
||||
h3 has successfully completed freeCodeCamp's
|
||||
h1
|
||||
strong Full Stack Development Projects
|
||||
h4 1 of 3 legacy freeCodeCamp certificates, representing approximately 400 hours of coursework
|
||||
|
||||
footer
|
||||
.row.signatures
|
||||
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
|
||||
p
|
||||
strong Quincy Larson
|
||||
p Executive Director, freeCodeCamp.org
|
||||
.row
|
||||
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/full-stack-certification
|
||||
|
@@ -0,0 +1,32 @@
|
||||
meta(name='viewport', content='width=device-width, initial-scale=1')
|
||||
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
|
||||
include styles
|
||||
|
||||
.certificate-wrapper.container
|
||||
.row
|
||||
header
|
||||
.col-md-5.col-sm-12
|
||||
.logo
|
||||
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
|
||||
.col-md-7.col-sm-12
|
||||
.issue-date Issued
|
||||
strong #{date}
|
||||
|
||||
section.information
|
||||
.information-container
|
||||
h3 This certifies that
|
||||
h1
|
||||
strong= name
|
||||
h3 has successfully completed freeCodeCamp's
|
||||
h1
|
||||
strong Information Security and Quality Assurance Projects
|
||||
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
|
||||
|
||||
footer
|
||||
.row.signatures
|
||||
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
|
||||
p
|
||||
strong Quincy Larson
|
||||
p Executive Director, freeCodeCamp.org
|
||||
.row
|
||||
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/information-security-and-quality-assurance-certification
|
@@ -0,0 +1,32 @@
|
||||
meta(name='viewport', content='width=device-width, initial-scale=1')
|
||||
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
|
||||
include styles
|
||||
|
||||
.certificate-wrapper.container
|
||||
.row
|
||||
header
|
||||
.col-md-5.col-sm-12
|
||||
.logo
|
||||
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
|
||||
.col-md-7.col-sm-12
|
||||
.issue-date Issued
|
||||
strong #{date}
|
||||
|
||||
section.information
|
||||
.information-container
|
||||
h3 This certifies that
|
||||
h1
|
||||
strong= name
|
||||
h3 has successfully completed freeCodeCamp's
|
||||
h1
|
||||
strong JavaScript Algorithms and Data Structures Certificate
|
||||
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
|
||||
|
||||
footer
|
||||
.row.signatures
|
||||
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
|
||||
p
|
||||
strong Quincy Larson
|
||||
p Executive Director, freeCodeCamp.org
|
||||
.row
|
||||
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/javascript-algorithms-and-data-structures-certification
|
32
server/views/certificate/responsive-web-design.jade
Normal file
32
server/views/certificate/responsive-web-design.jade
Normal file
@@ -0,0 +1,32 @@
|
||||
meta(name='viewport', content='width=device-width, initial-scale=1')
|
||||
link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css')
|
||||
include styles
|
||||
|
||||
.certificate-wrapper.container
|
||||
.row
|
||||
header
|
||||
.col-md-5.col-sm-12
|
||||
.logo
|
||||
img(class='img-responsive', src='https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg', alt="freeCodeCamp's Logo")
|
||||
.col-md-7.col-sm-12
|
||||
.issue-date Issued
|
||||
strong #{date}
|
||||
|
||||
section.information
|
||||
.information-container
|
||||
h3 This certifies that
|
||||
h1
|
||||
strong= name
|
||||
h3 has successfully completed freeCodeCamp's
|
||||
h1
|
||||
strong Responsive Web Design Projects
|
||||
h4 1 of 6 freeCodeCamp certificates, representing approximately 300 hours of coursework
|
||||
|
||||
footer
|
||||
.row.signatures
|
||||
img(class='img-responsive', src='https://i.imgur.com/OJFVJKg.png', alt="Quincy Larson's Signature")
|
||||
p
|
||||
strong Quincy Larson
|
||||
p Executive Director, freeCodeCamp.org
|
||||
.row
|
||||
p.verify Verify this certificate at: https://freecodecamp.org/#{username}/responsive-web-design-certification
|
@@ -70,6 +70,7 @@ style.
|
||||
.information {
|
||||
margin-top: -20px;
|
||||
height: 380px;
|
||||
text-align: center;
|
||||
background-color: #efefef;
|
||||
}
|
||||
|
||||
@@ -98,7 +99,7 @@ style.
|
||||
color: #006400;
|
||||
}
|
||||
|
||||
.signatures .col-md-6 {
|
||||
.signatures {
|
||||
text-align: center;
|
||||
margin: 0 auto;
|
||||
background-color: #efefef;
|
||||
@@ -183,7 +184,7 @@ style.
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 350px) {
|
||||
@media screen and (max-width: 675px) {
|
||||
.container {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
|
@@ -23,18 +23,24 @@ block content
|
||||
.col-xs-12.col-sm-6.col-sm-offset-3
|
||||
h3 Step 1: Which certification do you pledge to complete?
|
||||
.btn-group.btn-group-justified(data-toggle='buttons' role='group')
|
||||
label.btn.btn-primary.btn-lg.active
|
||||
input(type='radio' id=frontEndCert value=frontEndCert name='goal' checked="checked")
|
||||
| Front End
|
||||
label.btn.btn-primary.btn-lg
|
||||
input(type='radio' id=dataVisCert value=dataVisCert name='goal')
|
||||
| Data Vis
|
||||
label.btn.btn-primary.btn-lg
|
||||
input(type='radio' id=backEndCert value=backEndCert name='goal')
|
||||
| Back End
|
||||
label.btn.btn-primary.btn-lg
|
||||
input(type='radio' id=fullStackCert value=fullStackCert name='goal')
|
||||
| Full Stack
|
||||
label.btn.btn-primary.active
|
||||
input(type='radio' id="respWebDesignCert" value="Responsive Web Design Certification" name='goal' checked="checked")
|
||||
| Responsive Web Design
|
||||
label.btn.btn-primary
|
||||
input(type='radio' id="frontEndLibsCert" value="Front End Libraries Certification" name='goal')
|
||||
| Front End Libraries
|
||||
label.btn.btn-primary
|
||||
input(type='radio' id="jsAlgoDataStructCert" value="JavaScript Algorithms and Data Structures Certification" name='goal')
|
||||
| JavaScript Algorithms and Data Structures
|
||||
label.btn.btn-primary
|
||||
input(type='radio' id="dataVisCert" value="Data Visualization Certification" name='goal')
|
||||
| Data Visualization
|
||||
label.btn.btn-primary
|
||||
input(type='radio' id="apisMicroservicesCert" value="APIs and Microservices Certification" name='goal')
|
||||
| APIs and Microservices
|
||||
label.btn.btn-primary
|
||||
input(type='radio' id="infosecQaCert" value="Information Security and Quality Assurance Certification" name='goal')
|
||||
| Information Security and Quality Assurance
|
||||
.spacer
|
||||
.row
|
||||
.col-xs-12.col-sm-6.col-sm-offset-3
|
||||
|
58
server/views/dev-error.jade
Normal file
58
server/views/dev-error.jade
Normal file
@@ -0,0 +1,58 @@
|
||||
doctype html
|
||||
head
|
||||
title!= errorTitle
|
||||
style.
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 80px 100px;
|
||||
background: #ECE9E9 -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#ECE9E9));
|
||||
background: #ECE9E9 -moz-linear-gradient(top, #fff, #ECE9E9);
|
||||
background-repeat: no-repeat;
|
||||
color: #555;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
h1, h2 {
|
||||
font-size: 22px;
|
||||
color: #343434;
|
||||
}
|
||||
h1 em, h2 em {
|
||||
padding: 0 5px;
|
||||
font-weight: normal;
|
||||
}
|
||||
h1 {
|
||||
font-size: 60px;
|
||||
}
|
||||
h2 {
|
||||
margin-top: 10px;
|
||||
}
|
||||
ul li {
|
||||
list-style: none;
|
||||
}
|
||||
#trace {
|
||||
margin-top: 10px;
|
||||
margin-left: 60px;
|
||||
}
|
||||
body
|
||||
#wrapper
|
||||
h1!= title
|
||||
h2
|
||||
em!= status
|
||||
| #{errorTitle}
|
||||
h2
|
||||
em User Message:
|
||||
| #{message}
|
||||
h2
|
||||
em Type:
|
||||
| #{type}
|
||||
h2
|
||||
em redirect to:
|
||||
a(href=redirectTo) #{redirectTo}
|
||||
h2
|
||||
em stack trace:
|
||||
ul#trace!= stack
|
||||
|
@@ -1,7 +1,10 @@
|
||||
Camper <%= username %> has completed all three certifications!
|
||||
Camper <%= username %> has completed all six certifications!
|
||||
|
||||
Completed front end cert on <%= frontEndDate %>.
|
||||
Completed data vis cert on <%= dataVisDate %>.
|
||||
Completed back end cert on <%= backEndDate %>.
|
||||
Completed Responsive Web Design Certification on <%= responsiveWebDesignDate %>.
|
||||
Completed Front End Libraries Certification on <%= frontEndLibrariesDate %>.
|
||||
Completed JavaScript Algorithms and Data Structures Certification on <%= javascriptAlgorithmsDataStructuresDate %>.
|
||||
Completed Data Visualization Certification on <%= dataVisualizationDate %>.
|
||||
Completed API's and microservices Certification on <%= apisMicroservicesDate %>.
|
||||
Completed Information Security and Quality Assurance Certification on <%= infosecQADate %>.
|
||||
|
||||
https://www.freecodecamp.org/<%= username %>
|
||||
|
@@ -51,29 +51,29 @@ block content
|
||||
.spacer
|
||||
.row
|
||||
.text-center.negative-35
|
||||
.col-xs-12.col-sm-12.col-md-3
|
||||
.col-xs-12.col-sm-6.col-md-3
|
||||
.landing-skill-icon.ion-social-html5
|
||||
h2.black-text HTML5
|
||||
.col-xs-12.col-sm-12.col-md-3
|
||||
.col-xs-12.col-sm-6.col-md-3
|
||||
.landing-skill-icon.ion-social-css3
|
||||
h2.black-text CSS3
|
||||
.col-xs-12.col-sm-12.col-md-3
|
||||
.col-xs-12.col-sm-6.col-md-3
|
||||
.landing-skill-icon.ion-social-javascript
|
||||
h2.black-text JavaScript
|
||||
.col-xs-12.col-sm-12.col-md-3
|
||||
.col-xs-12.col-sm-6.col-md-3
|
||||
.landing-skill-icon.fa.fa-database.font-awesome-padding
|
||||
h2.black-text Databases
|
||||
.col-xs-12.col-sm-12.col-md-3
|
||||
.col-xs-12.col-sm-6.col-md-3
|
||||
.landing-skill-icon.ion-social-github
|
||||
h2.black-text Git & GitHub
|
||||
.col-xs-12.col-sm-12.col-md-3
|
||||
.col-xs-12.col-sm-6.col-md-3
|
||||
.landing-skill-icon.ion-social-nodejs
|
||||
h2.black-text Node.js
|
||||
.col-xs-12.col-sm-12.col-md-3
|
||||
.col-xs-12.col-sm-6.col-md-3
|
||||
.landing-skill-icon.custom-landing-skill-icon
|
||||
img(src='https://s3.amazonaws.com/freecodecamp/react.svg')
|
||||
h2.black-text React.js
|
||||
.col-xs-12.col-sm-12.col-md-3
|
||||
.col-xs-12.col-sm-6.col-md-3
|
||||
.landing-skill-icon.custom-landing-skill-icon
|
||||
img(src='https://s3.amazonaws.com/freecodecamp/d3-logo.svg')
|
||||
h2.black-text D3.js
|
||||
|
@@ -1,5 +1,5 @@
|
||||
.container
|
||||
.row.flashMessage.negative-30
|
||||
.row.flashMessage
|
||||
.col-xs-12.col-sm-8.col-sm-offset-2.col-md-6.col-md-offset-3
|
||||
if (messages.danger)
|
||||
.alert.alert-danger.fade.in
|
||||
|
Reference in New Issue
Block a user