Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| import _ from 'lodash'; | |
| import dedent from 'dedent'; | |
| import moment from 'moment-timezone'; | |
| import { Observable } from 'rx'; | |
| import debugFactory from 'debug'; | |
| import { | |
| frontEndChallengeId, | |
| dataVisChallengeId, | |
| backEndChallengeId | |
| } from '../utils/constantStrings.json'; | |
| import certTypes from '../utils/certTypes.json'; | |
| import { ifNoUser401, ifNoUserRedirectTo } from '../utils/middleware'; | |
| import { observeQuery } from '../utils/rx'; | |
| import { | |
| prepUniqueDays, | |
| calcCurrentStreak, | |
| calcLongestStreak | |
| } from '../utils/user-stats'; | |
| const debug = debugFactory('freecc:boot:user'); | |
| const sendNonUserToMap = ifNoUserRedirectTo('/map'); | |
| const certIds = { | |
| [certTypes.frontEnd]: frontEndChallengeId, | |
| [certTypes.dataVis]: dataVisChallengeId, | |
| [certTypes.backEnd]: backEndChallengeId | |
| }; | |
| 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' | |
| }; | |
| const certText = { | |
| [certTypes.fronEnd]: 'Front End certified', | |
| [certTypes.dataVis]: 'Data Vis Certified', | |
| [certTypes.backEnd]: 'Back End Certified', | |
| [certTypes.fullStack]: 'Full Stack Certified' | |
| }; | |
| const dateFormat = 'MMM DD, YYYY'; | |
| function replaceScriptTags(value) { | |
| return value | |
| .replace(/<script>/gi, 'fccss') | |
| .replace(/<\/script>/gi, 'fcces'); | |
| } | |
| function replaceFormAction(value) { | |
| return value.replace(/<form[^>]*>/, function(val) { | |
| return val.replace(/action(\s*?)=/, 'fccfaa$1='); | |
| }); | |
| } | |
| function encodeFcc(value = '') { | |
| return replaceScriptTags(replaceFormAction(value)); | |
| } | |
| module.exports = function(app) { | |
| var router = app.loopback.Router(); | |
| var User = app.models.User; | |
| function findUserByUsername$(username, fields) { | |
| return observeQuery( | |
| User, | |
| 'findOne', | |
| { | |
| where: { username }, | |
| fields | |
| } | |
| ); | |
| } | |
| router.get('/login', function(req, res) { | |
| res.redirect(301, '/signin'); | |
| }); | |
| router.get('/logout', function(req, res) { | |
| res.redirect(301, '/signout'); | |
| }); | |
| router.get('/signin', getSignin); | |
| router.get('/signout', signout); | |
| router.get('/forgot', getForgot); | |
| router.post('/forgot', postForgot); | |
| router.get('/reset-password', getReset); | |
| router.post('/reset-password', postReset); | |
| router.get('/email-signup', getEmailSignup); | |
| router.get('/email-signin', getEmailSignin); | |
| router.get( | |
| '/toggle-lockdown-mode', | |
| sendNonUserToMap, | |
| toggleLockdownMode | |
| ); | |
| router.post( | |
| '/account/delete', | |
| ifNoUser401, | |
| postDeleteAccount | |
| ); | |
| router.get( | |
| '/account', | |
| sendNonUserToMap, | |
| getAccount | |
| ); | |
| router.get('/vote1', vote1); | |
| router.get('/vote2', vote2); | |
| // Ensure these are the last routes! | |
| router.get( | |
| '/:username/front-end-certification', | |
| showCert.bind(null, certTypes.frontEnd) | |
| ); | |
| router.get( | |
| '/:username/data-visualization-certification', | |
| showCert.bind(null, certTypes.dataVis) | |
| ); | |
| router.get( | |
| '/:username/back-end-certification', | |
| showCert.bind(null, certTypes.backEnd) | |
| ); | |
| router.get( | |
| '/:username/full-stack-certification', | |
| (req, res) => res.redirect(req.url.replace('full-stack', 'back-end')) | |
| ); | |
| router.get('/:username', returnUser); | |
| app.use(router); | |
| function getSignin(req, res) { | |
| if (req.user) { | |
| return res.redirect('/'); | |
| } | |
| res.render('account/signin', { | |
| title: 'Sign in to Free Code Camp using a Social Media Account' | |
| }); | |
| } | |
| function signout(req, res) { | |
| req.logout(); | |
| res.redirect('/'); | |
| } | |
| function getEmailSignin(req, res) { | |
| if (req.user) { | |
| return res.redirect('/'); | |
| } | |
| res.render('account/email-signin', { | |
| title: 'Sign in to Free Code Camp using your Email Address' | |
| }); | |
| } | |
| function getEmailSignup(req, res) { | |
| if (req.user) { | |
| return res.redirect('/'); | |
| } | |
| res.render('account/email-signup', { | |
| title: 'Sign up for Free Code Camp using your Email Address' | |
| }); | |
| } | |
| function getAccount(req, res) { | |
| const { username } = req.user; | |
| return res.redirect('/' + username); | |
| } | |
| function returnUser(req, res, next) { | |
| const username = req.params.username.toLowerCase(); | |
| const { path } = req; | |
| User.findOne( | |
| { | |
| where: { username }, | |
| include: 'pledge' | |
| }, | |
| function(err, profileUser) { | |
| if (err) { | |
| return next(err); | |
| } | |
| if (!profileUser) { | |
| req.flash('errors', { | |
| msg: `404: We couldn't find path ${ path }` | |
| }); | |
| console.log('404'); | |
| return res.redirect('/'); | |
| } | |
| profileUser = profileUser.toJSON(); | |
| // timezone of signed-in account | |
| // to show all date related components | |
| // using signed-in account's timezone | |
| // not of the profile she is viewing | |
| const timezone = req.user && | |
| req.user.timezone ? req.user.timezone : 'UTC'; | |
| const timestamps = profileUser | |
| .progressTimestamps | |
| .map(objOrNum => { | |
| return typeof objOrNum === 'number' ? | |
| objOrNum : | |
| objOrNum.timestamp; | |
| }); | |
| const uniqueDays = prepUniqueDays(timestamps, timezone); | |
| profileUser.currentStreak = calcCurrentStreak(uniqueDays, timezone); | |
| profileUser.longestStreak = calcLongestStreak(uniqueDays, timezone); | |
| const data = profileUser | |
| .progressTimestamps | |
| .map((objOrNum) => { | |
| return typeof objOrNum === 'number' ? | |
| objOrNum : | |
| objOrNum.timestamp; | |
| }) | |
| .filter((timestamp) => { | |
| return !!timestamp; | |
| }) | |
| .reduce((data, timeStamp) => { | |
| data[(timeStamp / 1000)] = 1; | |
| return data; | |
| }, {}); | |
| function filterAlgos(challenge) { | |
| // test if name starts with hike/waypoint/basejump/zipline | |
| // fix for bug that saved different challenges with incorrect | |
| // challenge types | |
| return !(/^(waypoint|hike|zipline|basejump)/i).test(challenge.name) && | |
| +challenge.challengeType === 5; | |
| } | |
| function filterProjects(challenge) { | |
| return +challenge.challengeType === 3 || | |
| +challenge.challengeType === 4; | |
| } | |
| const completedChallenges = profileUser.completedChallenges | |
| .filter(({ name }) => typeof name === 'string') | |
| .map(challenge => { | |
| challenge = { ...challenge }; | |
| if (challenge.completedDate) { | |
| challenge.completedDate = | |
| moment.tz(challenge.completedDate, timezone) | |
| .format(dateFormat); | |
| } | |
| if (challenge.lastUpdated) { | |
| challenge.lastUpdated = | |
| moment.tz(challenge.lastUpdated, timezone).format(dateFormat); | |
| } | |
| return challenge; | |
| }); | |
| const projects = completedChallenges.filter(filterProjects); | |
| const algos = completedChallenges.filter(filterAlgos); | |
| const challenges = completedChallenges | |
| .filter(challenge => !filterAlgos(challenge)) | |
| .filter(challenge => !filterProjects(challenge)); | |
| res.render('account/show', { | |
| title: 'Camper ' + profileUser.username + '\'s Code Portfolio', | |
| username: profileUser.username, | |
| name: profileUser.name, | |
| isMigrationGrandfathered: profileUser.isMigrationGrandfathered, | |
| isGithubCool: profileUser.isGithubCool, | |
| isLocked: !!profileUser.isLocked, | |
| pledge: profileUser.pledge, | |
| isFrontEndCert: profileUser.isFrontEndCert, | |
| isDataVisCert: profileUser.isDataVisCert, | |
| isBackEndCert: profileUser.isBackEndCert, | |
| isFullStackCert: profileUser.isFullStackCert, | |
| isHonest: profileUser.isHonest, | |
| location: profileUser.location, | |
| calender: data, | |
| github: profileUser.githubURL, | |
| linkedin: profileUser.linkedin, | |
| google: profileUser.google, | |
| facebook: profileUser.facebook, | |
| twitter: profileUser.twitter, | |
| picture: profileUser.picture, | |
| progressTimestamps: profileUser.progressTimestamps, | |
| projects, | |
| algos, | |
| challenges, | |
| moment, | |
| longestStreak: profileUser.longestStreak, | |
| currentStreak: profileUser.currentStreak, | |
| encodeFcc | |
| }); | |
| } | |
| ); | |
| } | |
| function showCert(certType, req, res, next) { | |
| const username = req.params.username.toLowerCase(); | |
| const { user } = req; | |
| Observable.just(user) | |
| .flatMap(user => { | |
| if (user && user.username === username) { | |
| return Observable.just(user); | |
| } | |
| return findUserByUsername$(username, { | |
| isGithubCool: true, | |
| isCheater: true, | |
| isLocked: true, | |
| isFrontEndCert: true, | |
| isDataVisCert: true, | |
| isBackEndCert: true, | |
| isFullStackCert: true, | |
| isHonest: true, | |
| completedChallenges: true, | |
| username: true, | |
| name: true | |
| }); | |
| }) | |
| .subscribe( | |
| (user) => { | |
| if (!user) { | |
| req.flash('errors', { | |
| msg: `We couldn't find the user with the username ${username}` | |
| }); | |
| return res.redirect('/'); | |
| } | |
| if (!user.isGithubCool) { | |
| req.flash('errors', { | |
| msg: dedent` | |
| This user needs to link GitHub with their account | |
| in order for others to be able to view their certificate. | |
| ` | |
| }); | |
| return res.redirect('back'); | |
| } | |
| if (user.isCheater) { | |
| req.flash('errors', { | |
| msg: dedent` | |
| Upon review, this account has been flagged for academic | |
| dishonesty. If you’re the owner of this account contact | |
| team@freecodecamp.com for details. | |
| ` | |
| }); | |
| return res.redirect(`/${user.username}`); | |
| } | |
| if (user.isLocked) { | |
| req.flash('errors', { | |
| msg: dedent` | |
| ${username} has chosen to make their profile | |
| private. They will need to make their profile public | |
| in order for others to be able to view their certificate. | |
| ` | |
| }); | |
| return res.redirect('back'); | |
| } | |
| if (!user.isHonest) { | |
| req.flash('errors', { | |
| msg: dedent` | |
| ${username} has not yet agreed to our Academic Honesty Pledge. | |
| ` | |
| }); | |
| return res.redirect('back'); | |
| } | |
| if (user[certType]) { | |
| // find challenge in user profile | |
| // if not found supply empty object | |
| // if found grab date | |
| // if no date use todays date | |
| var { completedDate = new Date() } = | |
| _.find( | |
| user.completedChallenges, | |
| { id: certIds[certType] } | |
| ) || {}; | |
| return res.render( | |
| certViews[certType], | |
| { | |
| username: user.username, | |
| date: moment(new Date(completedDate)).format('MMMM, Do YYYY'), | |
| name: user.name | |
| } | |
| ); | |
| } | |
| req.flash('errors', { | |
| msg: `Looks like user ${username} is not ${certText[certType]}` | |
| }); | |
| res.redirect('back'); | |
| }, | |
| next | |
| ); | |
| } | |
| function toggleLockdownMode(req, res, next) { | |
| if (req.user.isLocked === true) { | |
| req.user.isLocked = false; | |
| return req.user.save(function(err) { | |
| if (err) { return next(err); } | |
| req.flash('success', { | |
| msg: dedent` | |
| Other people can now view all your challenge solutions. | |
| You can change this back at any time in the "Manage My Account" | |
| section at the bottom of this page. | |
| ` | |
| }); | |
| res.redirect('/' + req.user.username); | |
| }); | |
| } | |
| req.user.isLocked = true; | |
| return req.user.save(function(err) { | |
| if (err) { return next(err); } | |
| req.flash('success', { | |
| msg: dedent` | |
| All your challenge solutions are now hidden from other people. | |
| You can change this back at any time in the "Manage My Account" | |
| section at the bottom of this page. | |
| ` | |
| }); | |
| res.redirect('/' + req.user.username); | |
| }); | |
| } | |
| function postDeleteAccount(req, res, next) { | |
| User.destroyById(req.user.id, function(err) { | |
| if (err) { return next(err); } | |
| req.logout(); | |
| req.flash('info', { msg: 'Your account has been deleted.' }); | |
| res.redirect('/'); | |
| }); | |
| } | |
| function getReset(req, res) { | |
| if (!req.accessToken) { | |
| req.flash('errors', { msg: 'access token invalid' }); | |
| return res.render('account/forgot'); | |
| } | |
| res.render('account/reset', { | |
| title: 'Reset your Password', | |
| accessToken: req.accessToken.id | |
| }); | |
| } | |
| function postReset(req, res, next) { | |
| const errors = req.validationErrors(); | |
| const { password } = req.body; | |
| if (errors) { | |
| req.flash('errors', errors); | |
| return res.redirect('back'); | |
| } | |
| User.findById(req.accessToken.userId, function(err, user) { | |
| if (err) { return next(err); } | |
| user.updateAttribute('password', password, function(err) { | |
| if (err) { return next(err); } | |
| debug('password reset processed successfully'); | |
| req.flash('info', { msg: 'password reset processed successfully' }); | |
| res.redirect('/'); | |
| }); | |
| }); | |
| } | |
| function getForgot(req, res) { | |
| if (req.isAuthenticated()) { | |
| return res.redirect('/'); | |
| } | |
| res.render('account/forgot', { | |
| title: 'Forgot Password' | |
| }); | |
| } | |
| function postForgot(req, res) { | |
| const errors = req.validationErrors(); | |
| const email = req.body.email.toLowerCase(); | |
| if (errors) { | |
| req.flash('errors', errors); | |
| return res.redirect('/forgot'); | |
| } | |
| User.resetPassword({ | |
| email: email | |
| }, function(err) { | |
| if (err) { | |
| req.flash('errors', err.message); | |
| return res.redirect('/forgot'); | |
| } | |
| req.flash('info', { | |
| msg: 'An e-mail has been sent to ' + | |
| email + | |
| ' with further instructions.' | |
| }); | |
| res.render('account/forgot'); | |
| }); | |
| } | |
| /* | |
| function updateUserStoryPictures(userId, picture, username, cb) { | |
| Story.find({ 'author.userId': userId }, function(err, stories) { | |
| if (err) { return cb(err); } | |
| const tasks = []; | |
| stories.forEach(function(story) { | |
| story.author.picture = picture; | |
| story.author.username = username; | |
| tasks.push(function(cb) { | |
| story.save(cb); | |
| }); | |
| }); | |
| async.parallel(tasks, function(err) { | |
| if (err) { | |
| return cb(err); | |
| } | |
| cb(); | |
| }); | |
| }); | |
| } | |
| */ | |
| function vote1(req, res, next) { | |
| if (req.user) { | |
| req.user.tshirtVote = 1; | |
| req.user.save(function(err) { | |
| if (err) { return next(err); } | |
| req.flash('success', { msg: 'Thanks for voting!' }); | |
| res.redirect('/map'); | |
| }); | |
| } else { | |
| req.flash('error', { msg: 'You must be signed in to vote.' }); | |
| res.redirect('/map'); | |
| } | |
| } | |
| function vote2(req, res, next) { | |
| if (req.user) { | |
| req.user.tshirtVote = 2; | |
| req.user.save(function(err) { | |
| if (err) { return next(err); } | |
| req.flash('success', { msg: 'Thanks for voting!' }); | |
| res.redirect('/map'); | |
| }); | |
| } else { | |
| req.flash('error', {msg: 'You must be signed in to vote.'}); | |
| res.redirect('/map'); | |
| } | |
| } | |
| }; |