2015-08-20 09:40:03 -07:00
import _ from 'lodash' ;
import async from 'async' ;
import moment from 'moment' ;
import debugFactory from 'debug' ;
2015-08-07 13:31:48 -07:00
2015-08-20 09:40:03 -07:00
import { ifNoUser401 } from '../utils/middleware' ;
2015-08-07 13:31:48 -07:00
2015-08-20 09:40:03 -07:00
const debug = debugFactory ( 'freecc:boot:user' ) ;
2015-08-19 13:05:53 -07:00
const daysBetween = 1.5 ;
2015-08-07 13:31:48 -07:00
function calcCurrentStreak ( cals ) {
2015-08-19 16:10:59 -07:00
const revCals = cals . concat ( [ Date . now ( ) ] ) . slice ( ) . reverse ( ) ;
2015-08-07 13:31:48 -07:00
let streakBroken = false ;
2015-08-19 13:05:53 -07:00
const lastDayInStreak = revCals
2015-08-07 13:31:48 -07:00
. reduce ( ( current , cal , index ) => {
2015-08-19 13:05:53 -07:00
const before = revCals [ index === 0 ? 0 : index - 1 ] ;
2015-08-07 13:31:48 -07:00
if (
! streakBroken &&
2015-08-19 13:05:53 -07:00
moment ( before ) . diff ( cal , 'days' , true ) < daysBetween
2015-08-07 13:31:48 -07:00
) {
2015-08-19 13:05:53 -07:00
return index ;
2015-08-07 13:31:48 -07:00
}
2015-08-19 13:45:32 -07:00
streakBroken = true ;
2015-08-19 13:05:53 -07:00
return current ;
} , 0 ) ;
const lastTimestamp = revCals [ lastDayInStreak ] ;
return Math . ceil ( moment ( ) . diff ( lastTimestamp , 'days' , true ) ) ;
}
2015-08-19 15:57:29 -07:00
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 ) ;
2015-08-07 13:31:48 -07:00
}
2015-05-25 19:02:09 -04:00
2015-06-03 16:19:23 -07:00
module . exports = function ( app ) {
2015-06-03 16:31:42 -07:00
var router = app . loopback . Router ( ) ;
2015-06-03 16:19:23 -07:00
var User = app . models . User ;
2015-06-03 17:14:45 -07:00
var Story = app . models . Story ;
2013-11-14 02:29:55 -05:00
2015-06-03 16:19:23 -07:00
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 ) ;
2015-08-16 09:54:34 -07:00
router . get ( '/reset-password' , getReset ) ;
router . post ( '/reset-password' , postReset ) ;
2015-06-03 16:19:23 -07:00
router . get ( '/email-signup' , getEmailSignup ) ;
router . get ( '/email-signin' , getEmailSignin ) ;
2015-09-27 17:08:56 -07:00
router . get ( '/toggle-lockdown-mode' , toggleLockdownMode ) ;
2015-08-20 09:40:03 -07:00
router . post (
'/account/delete' ,
ifNoUser401 ,
postDeleteAccount
) ;
2015-06-03 16:19:23 -07:00
router . get ( '/account/unlink/:provider' , getOauthUnlink ) ;
router . get ( '/account' , getAccount ) ;
2015-09-27 10:49:44 -07:00
router . get ( '/vote1' , vote1 ) ;
2015-09-27 14:00:55 -07:00
router . get ( '/vote2' , vote2 ) ;
2015-06-03 16:19:23 -07:00
// Ensure this is the last route!
router . get ( '/:username' , returnUser ) ;
2015-06-03 16:31:42 -07:00
app . use ( router ) ;
2015-07-22 23:27:18 -07:00
function getSignin ( req , res ) {
2015-06-03 16:19:23 -07:00
if ( req . user ) {
return res . redirect ( '/' ) ;
}
res . render ( 'account/signin' , {
title : 'Free Code Camp Login'
} ) ;
}
2014-01-13 04:34:54 -05:00
2015-07-22 23:27:18 -07:00
function signout ( req , res ) {
2015-06-03 16:19:23 -07:00
req . logout ( ) ;
res . redirect ( '/' ) ;
2015-03-28 11:34:12 +09:00
}
2014-01-07 17:45:42 -05:00
2015-07-22 23:27:18 -07:00
function getEmailSignin ( req , res ) {
2015-06-03 16:19:23 -07:00
if ( req . user ) {
return res . redirect ( '/' ) ;
}
res . render ( 'account/email-signin' , {
title : 'Sign in to your Free Code Camp Account'
} ) ;
2015-05-21 11:07:40 -07:00
}
2014-01-07 17:45:42 -05:00
2015-07-22 23:27:18 -07:00
function getEmailSignup ( req , res ) {
2015-06-03 16:19:23 -07:00
if ( req . user ) {
return res . redirect ( '/' ) ;
}
res . render ( 'account/email-signup' , {
title : 'Create Your Free Code Camp Account'
2015-03-21 13:42:02 +09:00
} ) ;
}
2014-12-23 08:48:28 -08:00
2015-07-22 23:27:18 -07:00
function getAccount ( req , res ) {
2015-09-08 22:52:32 -07:00
return res . redirect ( '/' + user . username ) ;
2015-06-03 16:19:23 -07:00
}
2015-01-24 04:14:41 -05:00
2015-07-22 23:27:18 -07:00
function returnUser ( req , res , next ) {
2015-08-05 13:01:19 -07:00
const username = req . params . username . toLowerCase ( ) ;
const { path } = req ;
2015-06-10 15:22:57 -07:00
User . findOne (
2015-08-05 13:01:19 -07:00
{ where : { username } } ,
2015-06-03 16:19:23 -07:00
function ( err , user ) {
if ( err ) {
return next ( err ) ;
}
2015-08-05 13:01:19 -07:00
if ( ! user ) {
req . flash ( 'errors' , {
msg : ` 404: We couldn't find path ${ path } `
} ) ;
return res . redirect ( '/' ) ;
}
2015-08-07 13:31:48 -07:00
var cals = user
. progressTimestamps
. map ( objOrNum => {
return typeof objOrNum === 'number' ?
objOrNum :
objOrNum . timestamp ;
} )
2015-08-19 13:05:53 -07:00
. sort ( ) ;
2015-08-05 13:01:19 -07:00
2015-08-07 13:31:48 -07:00
user . currentStreak = calcCurrentStreak ( cals ) ;
2015-08-19 15:57:29 -07:00
user . longestStreak = calcLongestStreak ( cals ) ;
2015-08-05 13:01:19 -07:00
2015-08-07 13:31:48 -07:00
const data = user
. progressTimestamps
. map ( ( objOrNum ) => {
return typeof objOrNum === 'number' ?
objOrNum :
objOrNum . timestamp ;
} )
2015-08-19 13:05:53 -07:00
. filter ( ( timestamp ) => {
return ! ! timestamp ;
} )
2015-08-07 13:31:48 -07:00
. reduce ( ( data , timeStamp ) => {
data [ ( timeStamp / 1000 ) ] = 1 ;
return data ;
} , { } ) ;
const challenges = user . completedChallenges . filter ( function ( obj ) {
2015-08-05 13:21:53 -07:00
return obj . challengeType === 3 || obj . challengeType === 4 ;
} ) ;
2015-08-05 13:01:19 -07:00
2015-08-18 14:53:18 -07:00
const bonfires = user . completedChallenges . filter ( function ( obj ) {
2015-08-18 19:23:43 -07:00
return obj . challengeType === 5 && ( obj . name || '' ) . match ( /Bonfire/g ) ;
2015-08-18 14:53:18 -07:00
} ) ;
2015-08-05 13:01:19 -07:00
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 ,
2015-08-21 16:08:49 -07:00
github : user . githubURL ,
linkedin : user . linkedin ,
google : user . google ,
facebook : user . facebook ,
twitter : user . twitter ,
2015-08-05 13:01:19 -07:00
picture : user . picture ,
progressTimestamps : user . progressTimestamps ,
calender : data ,
2015-08-05 13:21:53 -07:00
challenges : challenges ,
2015-08-18 14:53:18 -07:00
bonfires : bonfires ,
2015-08-05 13:01:19 -07:00
moment : moment ,
2015-08-07 13:31:48 -07:00
longestStreak : user . longestStreak ,
2015-09-30 21:04:16 -07:00
currentStreak : user . currentStreak ,
encodeURI : encodeURI
2015-08-05 13:01:19 -07:00
} ) ;
2015-05-21 11:07:40 -07:00
}
2015-06-03 16:19:23 -07:00
) ;
}
2014-01-28 17:41:13 -05:00
2015-09-27 17:08:56 -07:00
function toggleLockdownMode ( req , res ) {
if ( req . user ) {
if ( req . user . lockdownMode === true ) {
req . user . lockdownMode = false ;
req . user . save ( function ( err ) {
if ( err ) {
return next ( err ) ;
}
req . flash ( 'success' , { msg : '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 ) ;
} ) ;
} else {
req . user . lockdownMode = true ;
req . user . save ( function ( err ) {
if ( err ) {
return next ( err ) ;
}
req . flash ( 'success' , { msg : '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 ) ;
} ) ;
}
} else {
req . flash ( 'error' , { msg : 'You must be signed in to change your account settings.' } ) ;
res . redirect ( '/' ) ;
}
}
2015-07-22 23:27:18 -07:00
function postDeleteAccount ( req , res , next ) {
2015-06-03 16:19:23 -07:00
User . destroyById ( req . user . id , function ( err ) {
2015-03-21 13:42:02 +09:00
if ( err ) { return next ( err ) ; }
2015-06-03 16:19:23 -07:00
req . logout ( ) ;
req . flash ( 'info' , { msg : 'Your account has been deleted.' } ) ;
res . redirect ( '/' ) ;
2013-12-13 00:27:51 -05:00
} ) ;
2015-06-03 16:19:23 -07:00
}
2014-03-07 14:08:56 -05:00
2015-07-22 23:27:18 -07:00
function getOauthUnlink ( req , res , next ) {
2015-06-03 16:19:23 -07:00
var provider = req . params . provider ;
User . findById ( req . user . id , function ( err , user ) {
2015-03-21 13:42:02 +09:00
if ( err ) { return next ( err ) ; }
2015-06-03 16:19:23 -07:00
user [ provider ] = null ;
user . tokens =
_ . reject ( user . tokens , function ( token ) {
return token . kind === provider ;
2014-12-23 08:48:28 -08:00
} ) ;
2015-06-03 16:19:23 -07:00
user . save ( function ( err ) {
if ( err ) { return next ( err ) ; }
req . flash ( 'info' , { msg : provider + ' account has been unlinked.' } ) ;
res . redirect ( '/account' ) ;
2015-03-21 13:42:02 +09:00
} ) ;
} ) ;
2015-06-03 16:19:23 -07:00
}
2014-03-07 14:08:56 -05:00
2015-08-16 09:54:34 -07:00
function getReset ( req , res ) {
if ( ! req . accessToken ) {
req . flash ( 'errors' , { msg : 'access token invalid' } ) ;
return res . render ( 'account/forgot' ) ;
2015-06-03 16:19:23 -07:00
}
2015-08-16 09:54:34 -07:00
res . render ( 'account/reset' , {
title : 'Password Reset' ,
accessToken : req . accessToken . id
} ) ;
2015-03-21 13:42:02 +09:00
}
2014-03-07 14:08:56 -05:00
2015-07-22 23:27:18 -07:00
function postReset ( req , res , next ) {
2015-08-16 09:54:34 -07:00
const errors = req . validationErrors ( ) ;
const { password } = req . body ;
2015-03-21 13:42:02 +09:00
2015-06-03 16:19:23 -07:00
if ( errors ) {
req . flash ( 'errors' , errors ) ;
return res . redirect ( 'back' ) ;
}
2015-08-16 09:54:34 -07:00
User . findById ( req . accessToken . userId , function ( err , user ) {
2015-06-03 16:19:23 -07:00
if ( err ) { return next ( err ) ; }
2015-08-16 09:54:34 -07:00
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 ( '/' ) ;
} ) ;
2015-06-03 16:19:23 -07:00
} ) ;
}
2014-03-07 14:08:56 -05:00
2015-07-22 23:27:18 -07:00
function getForgot ( req , res ) {
2015-06-03 16:19:23 -07:00
if ( req . isAuthenticated ( ) ) {
return res . redirect ( '/' ) ;
}
res . render ( 'account/forgot' , {
title : 'Forgot Password'
} ) ;
2015-03-21 13:42:02 +09:00
}
2014-03-07 14:08:56 -05:00
2015-06-03 16:19:23 -07:00
/ * *
* POST / forgot
* Create a random token , then the send user an email with a reset link .
* /
2014-03-07 14:08:56 -05:00
2015-08-16 09:54:34 -07:00
function postForgot ( req , res ) {
const errors = req . validationErrors ( ) ;
const email = req . body . email . toLowerCase ( ) ;
2015-03-21 13:42:02 +09:00
2015-06-03 16:19:23 -07:00
if ( errors ) {
req . flash ( 'errors' , errors ) ;
return res . redirect ( '/forgot' ) ;
}
2015-03-21 13:42:02 +09:00
2015-08-16 09:54:34 -07:00
User . resetPassword ( {
email : email
} , function ( err ) {
if ( err ) {
req . flash ( 'errors' , err ) ;
return res . redirect ( '/forgot' ) ;
2015-06-03 16:19:23 -07:00
}
2015-08-16 09:54:34 -07:00
req . flash ( 'info' , {
msg : 'An e-mail has been sent to ' +
email +
' with further instructions.'
} ) ;
res . render ( 'account/forgot' ) ;
2015-06-03 16:19:23 -07:00
} ) ;
}
2015-06-03 17:14:45 -07:00
function updateUserStoryPictures ( userId , picture , username , cb ) {
2015-07-22 23:27:18 -07:00
Story . find ( { 'author.userId' : userId } , function ( err , stories ) {
2015-07-31 20:22:08 -07:00
if ( err ) { return cb ( err ) ; }
2015-06-03 17:14:45 -07:00
2015-07-31 20:22:08 -07:00
const tasks = [ ] ;
stories . forEach ( function ( story ) {
2015-06-03 17:14:45 -07:00
story . author . picture = picture ;
story . author . username = username ;
2015-07-22 23:27:18 -07:00
tasks . push ( function ( cb ) {
2015-06-03 17:14:45 -07:00
story . save ( cb ) ;
} ) ;
2015-07-31 20:22:08 -07:00
} ) ;
2015-07-22 23:27:18 -07:00
async . parallel ( tasks , function ( err ) {
2015-06-03 17:14:45 -07:00
if ( err ) {
return cb ( err ) ;
}
cb ( ) ;
} ) ;
2015-07-31 20:22:08 -07:00
} ) ;
2015-06-03 17:14:45 -07:00
}
2015-09-27 10:49:44 -07:00
function vote1 ( req , res ) {
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 ) {
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' ) ;
}
}
2015-06-03 16:19:23 -07:00
} ;