import dedent from 'dedent'; import moment from 'moment'; import debugFactory from 'debug'; import { ifNoUser401, ifNoUserRedirectTo } from '../utils/middleware'; const debug = debugFactory('freecc:boot:user'); const daysBetween = 1.5; const sendNonUserToMap = ifNoUserRedirectTo('/map'); function calcCurrentStreak(cals) { const revCals = cals.concat([Date.now()]).slice().reverse(); let streakBroken = false; const lastDayInStreak = revCals .reduce((current, cal, index) => { const before = revCals[index === 0 ? 0 : index - 1]; if ( !streakBroken && moment(before).diff(cal, 'days', true) < daysBetween ) { return index; } streakBroken = true; return current; }, 0); const lastTimestamp = revCals[lastDayInStreak]; return Math.ceil(moment().diff(lastTimestamp, 'days', true)); } function calcLongestStreak(cals) { let tail = cals[0]; const longest = cals.reduce((longest, head, index) => { const last = cals[index === 0 ? 0 : index - 1]; // is streak broken if (moment(head).diff(last, 'days', true) > daysBetween) { tail = head; } if (dayDiff(longest) < dayDiff([head, tail])) { return [head, tail]; } return longest; }, [cals[0], cals[0]]); return Math.ceil(dayDiff(longest)); } function dayDiff([head, tail]) { return moment(head).diff(tail, 'days', true); } module.exports = function(app) { var router = app.loopback.Router(); var User = app.models.User; // var Story = app.models.Story; 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 this is the last route! router.get('/:username', returnUser); app.use(router); function getSignin(req, res) { if (req.user) { return res.redirect('/'); } res.render('account/signin', { title: 'Free Code Camp Login' }); } 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 your Free Code Camp Account' }); } function getEmailSignup(req, res) { if (req.user) { return res.redirect('/'); } res.render('account/email-signup', { title: 'Create Your Free Code Camp Account' }); } 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 } }, function(err, profileUser) { if (err) { return next(err); } if (!profileUser) { req.flash('errors', { msg: `404: We couldn't find path ${ path }` }); return res.redirect('/'); } var cals = profileUser .progressTimestamps .map(objOrNum => { return typeof objOrNum === 'number' ? objOrNum : objOrNum.timestamp; }) .sort(); profileUser.currentStreak = calcCurrentStreak(cals); profileUser.longestStreak = calcLongestStreak(cals); 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; }, {}); const baseAndZip = profileUser.completedChallenges.filter( function(obj) { return obj.challengeType === 3 || obj.challengeType === 4; } ); const bonfires = profileUser.completedChallenges.filter(function(obj) { return obj.challengeType === 5 && (obj.name || '').match(/Bonfire/g); }); const waypoints = profileUser.completedChallenges.filter(function(obj) { return (obj.name || '').match(/^Waypoint/i); }); 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, 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, baseAndZip, bonfires, waypoints, moment, longestStreak: profileUser.longestStreak, currentStreak: profileUser.currentStreak }); } ); } 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: 'Password Reset', 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' }); } /** * 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(); if (errors) { req.flash('errors', errors); return res.redirect('/forgot'); } User.resetPassword({ email: email }, function(err) { if (err) { req.flash('errors', err); 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'); } } };