var _ = require('lodash'), async = require('async'), moment = require('moment'), debug = require('debug')('freecc:cntr:userController'); const daysBetween = 1.5; function calcCurrentStreak(cals) { const revCals = cals.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('/account/api', getAccountAngular); router.post('/account/password', postUpdatePassword); router.post('/account/delete', postDeleteAccount); router.get('/account/unlink/:provider', getOauthUnlink); router.get('/account', getAccount); // 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) { if (!req.user) { return res.redirect('/'); } res.render('account/account', { title: 'Manage your Free Code Camp Account' }); } function getAccountAngular(req, res) { res.json({ user: req.user || {} }); } function returnUser(req, res, next) { const username = req.params.username.toLowerCase(); const { path } = req; User.findOne( { where: { username } }, function(err, user) { if (err) { return next(err); } if (!user) { req.flash('errors', { msg: `404: We couldn't find path ${ path }` }); return res.redirect('/'); } if (!user.isGithubCool && !user.isMigrationGrandfathered) { req.flash('errors', { msg: ` user ${ username } has not completed account signup ` }); return res.redirect('/'); } var cals = user .progressTimestamps .map(objOrNum => { return typeof objOrNum === 'number' ? objOrNum : objOrNum.timestamp; }) .sort(); user.currentStreak = calcCurrentStreak(cals); user.longestStreak = calcLongestStreak(cals); const data = user .progressTimestamps .map((objOrNum) => { return typeof objOrNum === 'number' ? objOrNum : objOrNum.timestamp; }) .filter((timestamp) => { return !!timestamp; }) .reduce((data, timeStamp) => { data[(timeStamp / 1000)] = 1; return data; }, {}); const challenges = user.completedChallenges.filter(function(obj) { return obj.challengeType === 3 || obj.challengeType === 4; }); const bonfires = user.completedChallenges.filter(function(obj) { return obj.challengeType === 5 && (obj.name || '').match(/Bonfire/g); }); res.render('account/show', { title: 'Camper ' + user.username + '\'s portfolio', username: user.username, name: user.name, isMigrationGrandfathered: user.isMigrationGrandfathered, isGithubCool: user.isGithubCool, location: user.location, githubProfile: user.github, linkedinProfile: user.linkedin, googleProfile: user.google, facebookProfile: user.facebook, twitterHandle: user.twitter, picture: user.picture, progressTimestamps: user.progressTimestamps, calender: data, challenges: challenges, bonfires: bonfires, moment: moment, longestStreak: user.longestStreak, currentStreak: user.currentStreak }); } ); } function postUpdatePassword(req, res, next) { req.assert('password', 'Password must be at least 4 characters long') .len(4); req.assert('confirmPassword', 'Passwords do not match') .equals(req.body.password); var errors = req.validationErrors(); if (errors) { req.flash('errors', errors); return res.redirect('/account'); } User.findById(req.user.id, function(err, user) { if (err) { return next(err); } user.password = req.body.password; user.save(function(err) { if (err) { return next(err); } req.flash('success', { msg: 'Password has been changed.' }); res.redirect('/account'); }); }); } 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 getOauthUnlink(req, res, next) { var provider = req.params.provider; User.findById(req.user.id, function(err, user) { if (err) { return next(err); } user[provider] = null; user.tokens = _.reject(user.tokens, function(token) { return token.kind === provider; }); user.save(function(err) { if (err) { return next(err); } req.flash('info', { msg: provider + ' account has been unlinked.' }); res.redirect('/account'); }); }); } 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(); }); }); } };