2014-01-24 14:48:00 -05:00
mail = require ' ../commons/mail '
2014-07-16 15:13:21 -04:00
MailSent = require ' ../mail/sent/MailSent '
2014-07-18 14:05:37 -04:00
UserRemark = require ' ../users/remarks/UserRemark '
2014-06-19 11:38:07 -04:00
User = require ' ../users/User '
2014-07-16 15:13:21 -04:00
async = require ' async '
2014-01-24 14:48:00 -05:00
errors = require ' ../commons/errors '
2014-01-24 15:53:41 -05:00
config = require ' ../../server_config '
2014-06-19 11:38:07 -04:00
LevelSession = require ' ../levels/sessions/LevelSession '
Level = require ' ../levels/Level '
2014-03-08 21:49:09 -05:00
log = require ' winston '
sendwithus = require ' ../sendwithus '
2014-07-17 18:50:29 -04:00
if config . isProduction and config . redis . host isnt ' localhost '
2014-07-16 15:13:21 -04:00
lockManager = require ' ../commons/LockManager '
2014-07-17 18:50:29 -04:00
2014-02-04 16:29:13 -05:00
module.exports.setup = (app) ->
2014-03-08 21:49:09 -05:00
app . all config . mail . mailchimpWebhook , handleMailchimpWebHook
app . get ' /mail/cron/ladder-update ' , handleLadderUpdate
2014-07-16 15:13:21 -04:00
if lockManager
setupScheduledEmails ( )
2014-07-17 18:50:29 -04:00
2014-07-16 15:13:21 -04:00
setupScheduledEmails = ->
testForLockManager ( )
2014-07-16 19:37:06 -04:00
mailTasks = [
2014-10-02 15:09:51 -04:00
# taskFunction: candidateUpdateProfileTask
# frequencyMs: 10 * 60 * 1000 #10 minutes
#,
# taskFunction: internalCandidateUpdateTask
# frequencyMs: 10 * 60 * 1000 #10 minutes
#,
# taskFunction: employerNewCandidatesAvailableTask
# frequencyMs: 10 * 60 * 1000 #10 minutes
#,
# taskFunction: unapprovedCandidateFinishProfileTask
# frequencyMs: 10 * 60 * 1000
#,
# taskFunction: emailUserRemarkTaskRemindersTask
# frequencyMs: 10 * 60 * 1000
2014-07-16 19:37:06 -04:00
]
for mailTask in mailTasks
2014-07-17 18:50:29 -04:00
setInterval mailTask . taskFunction , mailTask . frequencyMs
2014-07-16 15:13:21 -04:00
testForLockManager = -> unless lockManager then throw " The system isn ' t configured to do distributed locking! "
2014-07-16 18:50:31 -04:00
2014-07-18 14:07:11 -04:00
### Approved Candidate Update Reminder Task ###
2014-07-16 18:50:31 -04:00
candidateUpdateProfileTask = ->
mailTaskName = " candidateUpdateProfileTask "
2014-07-17 18:50:29 -04:00
lockDurationMs = 2 * 60 * 1000
2014-07-16 18:50:31 -04:00
currentDate = new Date ( )
timeRanges = [ ]
2014-07-17 12:28:34 -04:00
for weekPair in [ [ 4 , 2 , ' two weeks ' ] , [ 8 , 4 , ' four weeks ' ] , [ 52 , 8 , ' eight weeks ' ] ]
2014-07-16 18:50:31 -04:00
timeRanges . push
start: generateWeekOffset currentDate , weekPair [ 0 ]
end: generateWeekOffset currentDate , weekPair [ 1 ]
name: weekPair [ 2 ]
lockManager . setLock mailTaskName , lockDurationMs , (err) ->
2014-07-16 22:46:06 -04:00
if err ? then return log . error " Error getting a distributed lock for task #{ mailTaskName } : #{ err } "
2014-07-16 18:50:31 -04:00
async . each timeRanges , emailTimeRange . bind ( { mailTaskName: mailTaskName } ) , (err) ->
if err
log . error " There was an error sending the candidate profile update reminder emails: #{ err } "
else
log . info " Completed mail task #{ mailTaskName } "
lockManager . releaseLock mailTaskName , (err) ->
if err ? then return log . error " There was an error releasing the distributed lock for task #{ mailTaskName } : #{ err } "
generateWeekOffset = (originalDate, numberOfWeeks) ->
return ( new Date ( originalDate . getTime ( ) - numberOfWeeks * 7 * 24 * 60 * 60 * 1000 ) ) . toISOString ( )
emailTimeRange = (timeRange, emailTimeRangeCallback) ->
2014-07-16 15:13:21 -04:00
waterfallContext =
" timeRange " : timeRange
" mailTaskName " : @ mailTaskName
async . waterfall [
findAllCandidatesWithinTimeRange . bind ( waterfallContext )
2014-07-17 18:50:29 -04:00
(unfilteredCandidates, cb) ->
2014-07-16 18:50:31 -04:00
async . reject unfilteredCandidates , candidateFilter . bind ( waterfallContext ) , cb . bind ( null , null )
(filteredCandidates, cb) ->
2014-07-16 15:13:21 -04:00
async . each filteredCandidates , sendReminderEmailToCandidate . bind ( waterfallContext ) , cb
2014-07-16 18:50:31 -04:00
] , emailTimeRangeCallback
2014-07-17 18:50:29 -04:00
2014-07-16 15:13:21 -04:00
findAllCandidatesWithinTimeRange = (cb) ->
findParameters =
" jobProfile.updated " :
$gt: @ timeRange . start
$lte: @ timeRange . end
2014-07-16 16:14:09 -04:00
" jobProfileApproved " : true
2014-07-16 19:37:06 -04:00
selection = " _id email jobProfile.name jobProfile.updated emails " #make sure to check for anyNotes too.
2014-07-16 15:13:21 -04:00
User . find ( findParameters ) . select ( selection ) . lean ( ) . exec cb
2014-07-17 18:50:29 -04:00
2014-07-16 15:13:21 -04:00
candidateFilter = (candidate, sentEmailFilterCallback) ->
2014-07-16 19:37:06 -04:00
if candidate . emails ? . anyNotes ? . enabled is false or candidate . emails ? . recruitNotes ? . enabled is false
return sentEmailFilterCallback true
2014-07-16 15:13:21 -04:00
findParameters =
" user " : candidate . _id
" mailTask " : @ mailTaskName
" metadata.timeRangeName " : @ timeRange . name
" metadata.updated " : candidate . jobProfile . updated
MailSent . find ( findParameters ) . lean ( ) . exec (err, sentMail) ->
2014-07-16 18:50:31 -04:00
if err ?
log . error " Error finding mail sent for task #{ @ mailTaskName } and user #{ candidate . _id } ! "
sentEmailFilterCallback true
else
sentEmailFilterCallback Boolean ( sentMail . length )
2014-07-09 19:24:14 -04:00
2014-07-16 15:13:21 -04:00
findEmployersSignedUpAfterDate = (dateObject, cb) ->
countParameters =
$or: [ { " dateCreated " : { $gte: dateObject } } , { " signedEmployerAgreement " : { $gte: dateObject } } ]
employerAt: { $exists: true }
permissions: " employer "
User . count countParameters , cb
2014-07-17 18:50:29 -04:00
2014-07-16 15:13:21 -04:00
sendReminderEmailToCandidate = (candidate, sendEmailCallback) ->
findEmployersSignedUpAfterDate new Date ( candidate . jobProfile . updated ) , (err, employersAfterCount) =>
2014-07-16 18:50:31 -04:00
if err ?
log . error " There was an error finding employers who signed up after #{ candidate . jobProfile . updated } : #{ err } "
return sendEmailCallback err
2014-07-16 22:46:06 -04:00
if employersAfterCount < 2
employersAfterCount = 2
2014-07-16 15:13:21 -04:00
context =
email_id: " tem_CtTLsKQufxrxoPMn7upKiL "
recipient:
address: candidate . email
name: candidate . jobProfile . name
email_data:
new_company: employersAfterCount
2014-07-16 18:50:31 -04:00
company_name: " CodeCombat "
user_profile: " http://codecombat.com/account/profile/ #{ candidate . _id } "
2014-07-16 22:46:06 -04:00
recipient_address: encodeURIComponent ( candidate . email )
2014-07-16 15:13:21 -04:00
log . info " Sending #{ @ timeRange . name } update reminder to #{ context . recipient . name } ( #{ context . recipient . address } ) "
newSentMail =
mailTask: @ mailTaskName
user: candidate . _id
metadata:
timeRangeName: @ timeRange . name
updated: candidate . jobProfile . updated
MailSent . create newSentMail , (err) ->
if err ? then return sendEmailCallback err
sendwithus . api . send context , (err, result) ->
2014-07-16 16:56:23 -04:00
log . error " Error sending candidate update reminder email: #{ err } with result #{ result } " if err
sendEmailCallback null
2014-07-18 14:05:37 -04:00
### End Approved Candidate Update Reminder Task ###
2014-08-30 18:19:42 -04:00
2014-07-18 14:05:37 -04:00
### Unapproved Candidate Finish Reminder Task ###
unapprovedCandidateFinishProfileTask = ->
mailTaskName = " unapprovedCandidateFinishProfileTask "
lockDurationMs = 2 * 60 * 1000
currentDate = new Date ( )
timeRanges = [ ]
for weekPair in [ [ 4 , 2 , ' two weeks ' ] , [ 8 , 4 , ' four weeks ' ] , [ 52 , 8 , ' eight weeks ' ] ]
timeRanges . push
start: generateWeekOffset currentDate , weekPair [ 0 ]
end: generateWeekOffset currentDate , weekPair [ 1 ]
name: weekPair [ 2 ]
lockManager . setLock mailTaskName , lockDurationMs , (err) ->
if err ? then return log . error " Error getting a distributed lock for task #{ mailTaskName } : #{ err } "
async . each timeRanges , emailUnapprovedCandidateTimeRange . bind ( { mailTaskName: mailTaskName } ) , (err) ->
if err
log . error " There was an error sending the candidate profile update reminder emails: #{ err } "
else
log . info " Completed mail task #{ mailTaskName } "
lockManager . releaseLock mailTaskName , (err) ->
if err ? then return log . error " There was an error releasing the distributed lock for task #{ mailTaskName } : #{ err } "
emailUnapprovedCandidateTimeRange = (timeRange, emailTimeRangeCallback) ->
waterfallContext =
" timeRange " : timeRange
" mailTaskName " : @ mailTaskName
async . waterfall [
findAllUnapprovedCandidatesWithinTimeRange . bind ( waterfallContext )
(unfilteredCandidates, cb) ->
async . reject unfilteredCandidates , ignoredCandidateFilter , cb . bind ( null , null )
(unfilteredPotentialCandidates, cb) ->
async . reject unfilteredPotentialCandidates , unapprovedCandidateFilter . bind ( waterfallContext ) , cb . bind ( null , null )
(filteredCandidates, cb) ->
async . each filteredCandidates , sendReminderEmailToUnapprovedCandidate . bind ( waterfallContext ) , cb
] , emailTimeRangeCallback
findAllUnapprovedCandidatesWithinTimeRange = (cb) ->
findParameters =
" jobProfile " :
$exists: true
" jobProfile.updated " :
$gt: @ timeRange . start
$lte: @ timeRange . end
" jobProfileApproved " : false
selection = " _id email jobProfile.name jobProfile.updated emails "
User . find ( findParameters ) . select ( selection ) . lean ( ) . exec cb
ignoredCandidateFilter = (candidate, cb) ->
2014-08-30 18:19:42 -04:00
findParameters =
2014-07-18 14:05:37 -04:00
" user " : candidate . _id
" contactName " : " Ignore "
UserRemark . count findParameters , (err, results) ->
if err ? then return true
return cb Boolean ( results . length )
2014-08-30 18:19:42 -04:00
2014-07-18 14:05:37 -04:00
unapprovedCandidateFilter = (candidate, sentEmailFilterCallback) ->
if candidate . emails ? . anyNotes ? . enabled is false or candidate . emails ? . recruitNotes ? . enabled is false
return sentEmailFilterCallback true
findParameters =
" user " : candidate . _id
" mailTask " : @ mailTaskName
" metadata.timeRangeName " : @ timeRange . name
" metadata.updated " : candidate . jobProfile . updated
MailSent . find ( findParameters ) . lean ( ) . exec (err, sentMail) ->
if err ?
log . error " Error finding mail sent for task #{ @ mailTaskName } and user #{ candidate . _id } ! "
sentEmailFilterCallback true
else
sentEmailFilterCallback Boolean ( sentMail . length )
sendReminderEmailToUnapprovedCandidate = (candidate, sendEmailCallback) ->
if err ?
log . error " There was an error finding employers who signed up after #{ candidate . jobProfile . updated } : #{ err } "
return sendEmailCallback err
context =
email_id: " tem_RXyjzmc7S2HJH287pfoSPN "
recipient:
address: candidate . email
name: candidate . jobProfile . name
email_data:
user_profile: " http://codecombat.com/account/profile/ #{ candidate . _id } "
recipient_address: encodeURIComponent ( candidate . email )
log . info " Sending #{ @ timeRange . name } finish profile reminder to #{ context . recipient . name } ( #{ context . recipient . address } ) "
newSentMail =
mailTask: @ mailTaskName
user: candidate . _id
metadata:
timeRangeName: @ timeRange . name
updated: candidate . jobProfile . updated
MailSent . create newSentMail , (err) ->
if err ? then return sendEmailCallback err
sendwithus . api . send context , (err, result) ->
log . error " Error sending candidate finish profile reminder email: #{ err } with result #{ result } " if err
sendEmailCallback null
### End Unapproved Candidate Finish Reminder Task ###
2014-08-30 18:19:42 -04:00
2014-07-16 15:13:21 -04:00
### Internal Candidate Update Reminder Email ###
2014-07-16 18:50:31 -04:00
internalCandidateUpdateTask = ->
mailTaskName = " internalCandidateUpdateTask "
2014-07-17 18:50:29 -04:00
lockDurationMs = 2 * 60 * 1000
2014-07-16 18:50:31 -04:00
lockManager . setLock mailTaskName , lockDurationMs , (err) ->
2014-07-16 22:46:06 -04:00
if err ? then return log . error " Error getting a distributed lock for task #{ mailTaskName } : #{ err } "
2014-07-16 20:02:40 -04:00
emailInternalCandidateUpdateReminder . call { " mailTaskName " : mailTaskName } , (err) ->
2014-07-16 18:50:31 -04:00
if err
log . error " There was an error sending the internal candidate update reminder.: #{ err } "
else
log . info " Sent internal candidate update reminder email! "
lockManager . releaseLock mailTaskName , (err) ->
if err ? then return log . error " There was an error releasing the distributed lock for task #{ mailTaskName } : #{ err } "
2014-07-16 15:13:21 -04:00
2014-07-16 18:50:31 -04:00
emailInternalCandidateUpdateReminder = (internalCandidateUpdateReminderCallback) ->
2014-07-16 15:13:21 -04:00
currentTime = new Date ( )
beginningOfUTCDay = new Date ( )
beginningOfUTCDay . setUTCHours ( 0 , 0 , 0 , 0 )
asyncContext =
" beginningOfUTCDay " : beginningOfUTCDay
" currentTime " : currentTime
" mailTaskName " : @ mailTaskName
async . waterfall [
2014-07-16 16:14:09 -04:00
findNonApprovedCandidatesWhoUpdatedJobProfileToday . bind ( asyncContext )
2014-07-16 15:13:21 -04:00
(unfilteredCandidates, cb) ->
async . reject unfilteredCandidates , candidatesUpdatedTodayFilter . bind ( asyncContext ) , cb . bind ( null , null )
(filteredCandidates, cb) ->
async . each filteredCandidates , sendInternalCandidateUpdateReminder . bind ( asyncContext ) , cb
2014-07-16 18:50:31 -04:00
] , internalCandidateUpdateReminderCallback
2014-07-17 18:50:29 -04:00
2014-07-16 16:14:09 -04:00
findNonApprovedCandidatesWhoUpdatedJobProfileToday = (cb) ->
2014-07-17 18:50:29 -04:00
findParameters =
2014-07-16 15:13:21 -04:00
" jobProfile.updated " :
$lte: @ currentTime . toISOString ( )
2014-07-16 20:02:40 -04:00
$gt: @ beginningOfUTCDay . toISOString ( )
2014-07-17 18:50:29 -04:00
" jobProfileApproved " : false
2014-07-16 15:13:21 -04:00
User . find ( findParameters ) . select ( " _id jobProfile.name jobProfile.updated " ) . lean ( ) . exec cb
2014-07-17 18:50:29 -04:00
2014-07-16 15:13:21 -04:00
candidatesUpdatedTodayFilter = (candidate, cb) ->
findParameters =
" user " : candidate . _id
" mailTask " : @ mailTaskName
2014-07-17 18:50:29 -04:00
" metadata.beginningOfUTCDay " : @ beginningOfUTCDay
2014-07-16 15:13:21 -04:00
MailSent . find ( findParameters ) . lean ( ) . exec (err, sentMail) ->
2014-07-16 18:50:31 -04:00
if err ?
log . error " Error finding mail sent for task #{ @ mailTaskName } and user #{ candidate . _id } ! "
cb true
else
cb Boolean ( sentMail . length )
2014-07-17 18:50:29 -04:00
2014-07-16 15:13:21 -04:00
sendInternalCandidateUpdateReminder = (candidate, cb) ->
context =
email_id: " tem_Ac7nhgKqatTHBCgDgjF5pE "
2014-07-17 18:50:29 -04:00
recipient:
2014-07-17 10:17:25 -04:00
address: " team@codecombat.com "
2014-07-16 15:13:21 -04:00
name: " The CodeCombat Team "
2014-07-16 18:50:31 -04:00
email_data:
2014-07-18 22:22:00 -04:00
new_candidate_profile: " http://codecombat.com/account/profile/ #{ candidate . _id } "
2014-07-16 15:13:21 -04:00
log . info " Sending candidate updated reminder for #{ candidate . jobProfile . name } "
newSentMail =
mailTask: @ mailTaskName
user: candidate . _id
metadata:
beginningOfUTCDay: @ beginningOfUTCDay
2014-07-17 18:50:29 -04:00
2014-07-16 15:13:21 -04:00
MailSent . create newSentMail , (err) ->
if err ? then return cb err
sendwithus . api . send context , (err, result) ->
2014-07-16 16:56:23 -04:00
log . error " Error sending interal candidate update email: #{ err } with result #{ result } " if err
cb null
2014-07-16 18:50:31 -04:00
2014-07-16 15:13:21 -04:00
### End Internal Candidate Update Reminder Email ###
### Employer New Candidates Available Email ###
2014-07-16 18:50:31 -04:00
employerNewCandidatesAvailableTask = ->
mailTaskName = " employerNewCandidatesAvailableTask "
2014-07-17 18:50:29 -04:00
lockDurationMs = 2 * 60 * 1000
2014-07-16 18:50:31 -04:00
lockManager . setLock mailTaskName , lockDurationMs , (err) ->
2014-07-16 22:46:06 -04:00
if err ? then return log . error " Error getting a distributed lock for task #{ mailTaskName } : #{ err } "
2014-07-16 20:02:40 -04:00
emailEmployerNewCandidatesAvailable . call { " mailTaskName " : mailTaskName } , (err) ->
2014-07-16 18:50:31 -04:00
if err
log . error " There was an error completing the new candidates available task: #{ err } "
else
log . info " Completed the employer new candidates available task! "
lockManager . releaseLock mailTaskName , (err) ->
if err ? then return log . error " There was an error releasing the distributed lock for task #{ mailTaskName } : #{ err } "
2014-07-16 16:14:09 -04:00
2014-07-16 18:50:31 -04:00
emailEmployerNewCandidatesAvailable = (emailEmployerNewCandidatesAvailableCallback) ->
2014-07-16 16:14:09 -04:00
currentTime = new Date ( )
2014-07-17 18:50:29 -04:00
asyncContext =
2014-07-16 16:14:09 -04:00
" currentTime " : currentTime
" mailTaskName " : @ mailTaskName
2014-07-17 18:50:29 -04:00
2014-07-16 16:14:09 -04:00
async . waterfall [
findAllEmployers
makeEmployerNamesEasilyAccessible
(allEmployers, cb) ->
async . reject allEmployers , employersEmailedDigestMoreThanWeekAgoFilter . bind ( asyncContext ) , cb . bind ( null , null )
(employersToEmail, cb) ->
async . each employersToEmail , sendEmployerNewCandidatesAvailableEmail . bind ( asyncContext ) , cb
2014-07-16 18:50:31 -04:00
] , emailEmployerNewCandidatesAvailableCallback
2014-07-17 18:50:29 -04:00
2014-07-16 16:14:09 -04:00
findAllEmployers = (cb) ->
2014-07-17 18:50:29 -04:00
findParameters =
2014-07-16 16:14:09 -04:00
" employerAt " :
2014-07-17 18:50:29 -04:00
$exists: true
2014-07-16 16:14:09 -04:00
permissions: " employer "
2014-07-16 19:37:06 -04:00
selection = " _id email employerAt signedEmployerAgreement.data.firstName signedEmployerAgreement.data.lastName activity dateCreated emails "
2014-07-16 16:54:05 -04:00
User . find ( findParameters ) . select ( selection ) . lean ( ) . exec cb
2014-07-17 18:50:29 -04:00
2014-07-16 16:14:09 -04:00
makeEmployerNamesEasilyAccessible = (allEmployers, cb) ->
for employer , index in allEmployers
if employer . signedEmployerAgreement ? . data ? . firstName
employer.name = employer . signedEmployerAgreement . data . firstName + " " + employer . signedEmployerAgreement . data . lastName
delete employer . signedEmployerAgreement
allEmployers [ index ] = employer
cb null , allEmployers
2014-07-17 18:50:29 -04:00
2014-07-16 16:14:09 -04:00
employersEmailedDigestMoreThanWeekAgoFilter = (employer, cb) ->
2014-07-16 19:37:06 -04:00
if employer . emails ? . employerNotes ? . enabled is false
2014-07-17 12:28:34 -04:00
return cb true
2014-07-17 12:50:18 -04:00
if not employer . signedEmployerAgreement and not employer . activity ? . login ?
return cb true
2014-07-17 18:50:29 -04:00
findParameters =
2014-07-16 16:14:09 -04:00
" user " : employer . _id
" mailTask " : @ mailTaskName
" sent " :
2014-07-21 11:13:09 -04:00
$gt: new Date ( @ currentTime . getTime ( ) - 14 * 24 * 60 * 60 * 1000 )
2014-07-16 16:14:09 -04:00
MailSent . find ( findParameters ) . lean ( ) . exec (err, sentMail) ->
2014-07-16 18:50:31 -04:00
if err ?
log . error " Error finding mail sent for task #{ @ mailTaskName } and employer # employer._id}! "
cb true
else
cb Boolean ( sentMail . length )
2014-07-16 16:14:09 -04:00
sendEmployerNewCandidatesAvailableEmail = (employer, cb) ->
2014-07-17 18:50:29 -04:00
lastLoginDate = employer . activity ? . login ? . last ? employer . dateCreated
2014-07-16 16:54:05 -04:00
countParameters =
" jobProfileApproved " : true
$or: [
2014-07-17 18:50:29 -04:00
jobProfileApprovedDate:
2014-07-16 16:54:05 -04:00
$gt: lastLoginDate . toISOString ( )
,
jobProfileApprovedDate:
$exists: false
" jobProfile.updated " :
2014-07-17 18:50:29 -04:00
$gt: lastLoginDate . toISOString ( )
2014-07-16 16:54:05 -04:00
]
User . count countParameters , (err, numberOfCandidatesSinceLogin) =>
if err ? then return cb err
2014-07-21 11:32:44 -04:00
if numberOfCandidatesSinceLogin < 4
return cb null
2014-07-16 16:54:05 -04:00
context =
email_id: " tem_CCcHKr95Nvu5bT7c7iHCtm "
recipient:
address: employer . email
email_data:
new_candidates: numberOfCandidatesSinceLogin
employer_company_name: employer . employerAt
company_name: " CodeCombat "
2014-07-16 22:46:06 -04:00
recipient_address: encodeURIComponent ( employer . email )
2014-07-16 18:50:31 -04:00
if employer . name
context.recipient.name = employer . name
2014-07-16 16:54:05 -04:00
log . info " Sending available candidates update reminder to #{ context . recipient . name } ( #{ context . recipient . address } ) "
newSentMail =
mailTask: @ mailTaskName
user: employer . _id
MailSent . create newSentMail , (err) ->
if err ? then return cb err
sendwithus . api . send context , (err, result) ->
2014-07-16 16:56:23 -04:00
log . error " Error sending employer candidates available email: #{ err } with result #{ result } " if err
cb null
2014-07-16 16:14:09 -04:00
2014-07-16 15:13:21 -04:00
### End Employer New Candidates Available Email ###
2014-08-30 18:19:42 -04:00
2014-07-18 17:50:31 -04:00
### Task Emails ###
2014-07-18 19:26:53 -04:00
emailUserRemarkTaskRemindersTask = ->
mailTaskName = " emailUserRemarkTaskRemindersTask "
lockDurationMs = 2 * 60 * 1000
lockManager . setLock mailTaskName , lockDurationMs , (err) ->
if err ? then return log . error " Error getting a distributed lock for task #{ mailTaskName } : #{ err } "
emailUserRemarkTaskReminders . call { " mailTaskName " : mailTaskName } , (err) ->
if err
log . error " There was an error completing the #{ mailTaskName } : #{ err } "
else
log . info " Completed the #{ mailTaskName } "
lockManager . releaseLock mailTaskName , (err) ->
if err ? then return log . error " There was an error releasing the distributed lock for task #{ mailTaskName } : #{ err } "
emailUserRemarkTaskReminders = (cb) ->
currentTime = new Date ( )
asyncContext =
" currentTime " : currentTime
" mailTaskName " : @ mailTaskName
2014-08-30 18:19:42 -04:00
2014-07-18 19:26:53 -04:00
async . waterfall [
findAllIncompleteUserRemarkTasksDue . bind ( asyncContext )
processRemarksIntoTasks . bind ( asyncContext )
(allTasks, cb) ->
async . reject allTasks , taskReminderAlreadySentThisWeekFilter . bind ( asyncContext ) , cb . bind ( null , null )
(tasksToRemind, cb) ->
async . each tasksToRemind , sendUserRemarkTaskEmail . bind ( asyncContext ) , cb
] , cb
2014-08-30 18:19:42 -04:00
2014-07-18 19:26:53 -04:00
findAllIncompleteUserRemarkTasksDue = (cb) ->
2014-08-30 18:19:42 -04:00
findParameters =
2014-07-18 19:26:53 -04:00
tasks:
$exists: true
$elemMatch:
date:
$lte: @ currentTime . toISOString ( )
status:
$ne: ' Completed '
selection = " contact user tasks "
UserRemark . find ( findParameters ) . select ( selection ) . lean ( ) . exec cb
2014-08-30 18:19:42 -04:00
2014-07-18 19:26:53 -04:00
processRemarksIntoTasks = (remarks, cb) ->
tasks = [ ]
for remark in remarks
for task in remark . tasks
2014-08-30 18:19:42 -04:00
taskObject =
2014-07-18 19:26:53 -04:00
date: task . date
action: task . action
contact: remark . contact
user: remark . user
remarkID: remark . _id
tasks . push taskObject
cb null , tasks
taskReminderAlreadySentThisWeekFilter = (task, cb) ->
findParameters =
" user " : task . contact
" mailTask " : @ mailTaskName
" sent " :
$gt: new Date ( @ currentTime . getTime ( ) - 7 * 24 * 60 * 60 * 1000 )
" metadata " :
remarkID: task . remarkID
taskAction: task . action
date: task . date
MailSent . count findParameters , (err, count) ->
if err ? then return cb true
return cb Boolean ( count )
2014-08-30 18:19:42 -04:00
2014-07-18 19:26:53 -04:00
sendUserRemarkTaskEmail = (task, cb) ->
mailTaskName = @ mailTaskName
User . findOne ( " _id " : task . contact ) . select ( " email " ) . lean ( ) . exec (err, contact) ->
if err ? then return cb err
2014-07-21 11:02:53 -04:00
User . findOne ( " _id " : task . user ) . select ( " jobProfile.name " ) . lean ( ) . exec (err, user) ->
2014-07-18 19:26:53 -04:00
if err ? then return cb err
2014-07-21 11:02:53 -04:00
context =
email_id: " tem_aryDjyw6JmEmbKtCMTSwAM "
recipient:
address: contact . email
email_data:
task_text: task . action
candidate_name: user . jobProfile ? . name ? " (Name not listed in job profile) "
candidate_link: " http://codecombat.com/account/profile/ #{ task . user } "
due_date: task . date
log . info " Sending recruitment task reminder to #{ contact . email } "
newSentMail =
mailTask: mailTaskName
user: task . contact
" metadata " :
remarkID: task . remarkID
taskAction: task . action
date: task . date
MailSent . create newSentMail , (err) ->
if err ? then return cb err
sendwithus . api . send context , (err, result) ->
log . error " Error sending #{ mailTaskName } to #{ contact . email } : #{ err } with result #{ result } " if err
cb null
2014-07-16 17:29:30 -04:00
2014-07-16 15:13:21 -04:00
### New Recruit Leaderboard Email ###
2014-07-16 17:29:30 -04:00
# ##
2014-07-16 15:13:21 -04:00
newRecruitLeaderboardEmailTask = ->
# tem_kMQFCKX3v4DNAQDsMAsPJC
#maxRank and maxRankTime should be recorded if isSimulating is false
mailTaskName = " newRecruitLeaderboardEmailTask "
lockDurationMs = 6000
lockManager . setLock mailTaskName , lockDurationMs , (err, lockResult) ->
2014-07-16 17:29:30 -04:00
# ##
2014-07-16 15:13:21 -04:00
### End New Recruit Leaderboard Email ###
2014-07-17 18:50:29 -04:00
### Employer Matching Candidate Notification Email ###
2014-07-16 17:29:30 -04:00
# ##
2014-07-16 15:13:21 -04:00
employerMatchingCandidateNotificationTask = ->
# tem_mYsepTfWQ265noKfZJcbBH
#save email filters in their own collection
mailTaskName = " employerMatchingCandidateNotificationTask "
lockDurationMs = 6000
lockManager . setLock mailTaskName , lockDurationMs , (err, lockResult) ->
2014-07-16 17:29:30 -04:00
# ##
2014-07-16 15:13:21 -04:00
### End Employer Matching Candidate Notification Email ###
### Ladder Update Email ###
2014-07-17 16:16:17 -04:00
### Employer ignore ###
2014-03-22 12:48:36 -04:00
DEBUGGING = false
LADDER_PREGAME_INTERVAL = 2 * 3600 * 1000 # Send emails two hours before players last submitted.
getTimeFromDaysAgo = (now, daysAgo) ->
t = now - 86400 * 1000 * daysAgo - LADDER_PREGAME_INTERVAL
2014-03-12 11:11:48 -04:00
isRequestFromDesignatedCronHandler = (req, res) ->
2014-06-30 22:16:26 -04:00
requestIP = req . headers [ ' x-forwarded-for ' ] ? . replace ( ' ' , ' ' ) . split ( ' , ' ) [ 0 ]
2014-04-07 12:51:31 -04:00
if requestIP isnt config . mail . cronHandlerPublicIP and requestIP isnt config . mail . cronHandlerPrivateIP
console . log " RECEIVED REQUEST FROM IP #{ requestIP } (headers indicate #{ req . headers [ ' x-forwarded-for ' ] } "
2014-06-30 22:16:26 -04:00
console . log ' UNAUTHORIZED ATTEMPT TO SEND TRANSACTIONAL LADDER EMAIL THROUGH CRON MAIL HANDLER '
res . send ( ' You aren \' t authorized to perform that action. Only the specified Cron handler may perform that action. ' )
2014-03-12 11:11:48 -04:00
res . end ( )
2014-03-13 19:11:44 -04:00
return false
return true
2014-03-13 21:50:52 -04:00
2014-07-16 18:50:31 -04:00
2014-07-17 18:50:29 -04:00
2014-03-08 21:49:09 -05:00
handleLadderUpdate = (req, res) ->
2014-06-30 22:16:26 -04:00
log . info ( ' Going to see about sending ladder update emails. ' )
2014-08-30 18:19:42 -04:00
requestIsFromDesignatedCronHandler = DEBUGGING or isRequestFromDesignatedCronHandler req , res
return unless requestIsFromDesignatedCronHandler
2014-03-13 21:50:52 -04:00
2014-03-09 00:29:41 -05:00
res . send ( ' Great work, Captain Cron! I can take it from here. ' )
res . end ( )
2014-03-09 21:46:11 -04:00
# TODO: somehow fetch the histograms
2014-04-07 12:51:31 -04:00
emailDays = [ 1 , 2 , 4 , 7 , 14 , 30 ]
2014-03-08 21:49:09 -05:00
now = new Date ( )
for daysAgo in emailDays
2014-03-09 00:29:41 -05:00
# Get every session that was submitted in a 5-minute window after the time.
2014-03-22 12:48:36 -04:00
startTime = getTimeFromDaysAgo now , daysAgo
2014-03-10 23:22:25 -04:00
endTime = startTime + 5 * 60 * 1000
2014-03-22 12:48:36 -04:00
if DEBUGGING
endTime = startTime + 15 * 60 * 1000 # Debugging: make sure there's something to send
2014-03-09 00:29:41 -05:00
findParameters = { submitted: true , submitDate: { $gt: new Date ( startTime ) , $lte: new Date ( endTime ) } }
2014-03-08 21:49:09 -05:00
# TODO: think about putting screenshots in the email
2014-08-30 18:19:42 -04:00
selectString = ' creator team levelName levelID totalScore matches submitted submitDate scoreHistory level.original '
2014-03-08 21:49:09 -05:00
query = LevelSession . find ( findParameters )
. select ( selectString )
. lean ( )
2014-03-09 00:29:41 -05:00
do (daysAgo) ->
query . exec (err, results) ->
if err
2014-03-10 16:20:00 -04:00
log . error " Couldn ' t fetch ladder updates for #{ findParameters } \n Error: #{ err } "
2014-03-09 00:29:41 -05:00
return errors . serverError res , " Ladder update email query failed: #{ JSON . stringify ( err ) } "
2014-03-10 16:20:00 -04:00
log . info " Found #{ results . length } ladder sessions to email updates about for #{ daysAgo } day(s) ago. "
2014-03-22 12:48:36 -04:00
sendLadderUpdateEmail result , now , daysAgo for result in results
2014-03-08 21:49:09 -05:00
2014-03-22 12:48:36 -04:00
sendLadderUpdateEmail = (session, now, daysAgo) ->
2014-06-30 22:16:26 -04:00
User . findOne ( { _id: session . creator } ) . select ( ' name email firstName lastName emailSubscriptions emails preferredLanguage ' ) . exec (err, user) ->
2014-03-08 21:49:09 -05:00
if err
2014-03-10 16:20:00 -04:00
log . error " Couldn ' t find user for #{ session . creator } from session #{ session . _id } "
return
2014-04-21 19:15:23 -04:00
allowNotes = user . isEmailSubscriptionEnabled ' anyNotes '
2014-05-05 18:11:00 -04:00
unless user . get ( ' email ' ) and allowNotes and not session . unsubscribed
log . info " Not sending email to #{ user . get ( ' email ' ) } #{ user . get ( ' name ' ) } because they only want emails about #{ user . get ( ' emailSubscriptions ' ) } , #{ user . get ( ' emails ' ) } - session unsubscribed: #{ session . unsubscribed } "
2014-03-08 21:49:09 -05:00
return
2014-03-10 23:22:25 -04:00
unless session . levelName
2014-05-05 18:11:00 -04:00
log . info " Not sending email to #{ user . get ( ' email ' ) } #{ user . get ( ' name ' ) } because the session had no levelName in it. "
2014-03-10 23:22:25 -04:00
return
2014-05-05 18:11:00 -04:00
name = if user . get ( ' firstName ' ) and user . get ( ' lastName ' ) then " #{ user . get ( ' firstName ' ) } " else user . get ( ' name ' )
2014-06-30 22:16:26 -04:00
name = ' Wizard ' if not name or name is ' Anoner '
2014-03-08 21:49:09 -05:00
2014-03-11 00:30:46 -04:00
# Fetch the most recent defeat and victory, if there are any.
# (We could look at strongest/weakest, but we'd have to fetch everyone, or denormalize more.)
2014-03-22 12:48:36 -04:00
matches = _ . filter session . matches , (match) -> match . date >= getTimeFromDaysAgo now , daysAgo
2014-03-11 00:30:46 -04:00
defeats = _ . filter matches , (match) -> match . metrics . rank is 1 and match . opponents [ 0 ] . metrics . rank is 0
2014-03-11 16:20:52 -04:00
victories = _ . filter matches , (match) -> match . metrics . rank is 0 and match . opponents [ 0 ] . metrics . rank is 1
2014-07-08 19:28:45 -04:00
2014-03-11 00:30:46 -04:00
defeat = _ . last defeats
victory = _ . last victories
2014-08-30 18:19:42 -04:00
sendEmail = (defeatContext, victoryContext, levelVersionsContext) ->
2014-03-08 21:49:09 -05:00
# TODO: do something with the preferredLanguage?
context =
email_id: sendwithus . templates . ladder_update_email
recipient:
2014-05-05 18:41:02 -04:00
address: if DEBUGGING then ' nick@codecombat.com ' else user . get ( ' email ' )
2014-03-09 00:29:41 -05:00
name: name
email_data:
name: name
days_ago: daysAgo
2014-03-11 00:30:46 -04:00
wins: victories . length
losses: defeats . length
2014-03-09 00:29:41 -05:00
total_score: Math . round ( session . totalScore * 100 )
team: session . team
2014-03-10 23:22:25 -04:00
team_name: session . team [ 0 ] . toUpperCase ( ) + session . team . substr ( 1 )
2014-03-09 00:29:41 -05:00
level_name: session . levelName
2014-03-11 00:30:46 -04:00
session_id: session . _id
2014-03-09 00:29:41 -05:00
ladder_url: " http://codecombat.com/play/ladder/ #{ session . levelID } # my-matches "
2014-03-11 01:03:33 -04:00
score_history_graph_url: getScoreHistoryGraphURL session , daysAgo
2014-03-09 00:29:41 -05:00
defeat: defeatContext
victory: victoryContext
2014-08-30 18:19:42 -04:00
levelVersions: levelVersionsContext
2014-03-11 22:17:58 -04:00
log . info " Sending ladder update email to #{ context . recipient . address } with #{ context . email_data . wins } wins and #{ context . email_data . losses } losses since #{ daysAgo } day(s) ago. "
2014-03-08 21:49:09 -05:00
sendwithus . api . send context , (err, result) ->
2014-03-10 16:20:00 -04:00
log . error " Error sending ladder update email: #{ err } with result #{ result } " if err
2014-03-08 21:49:09 -05:00
urlForMatch = (match) ->
2014-03-09 00:29:41 -05:00
" http://codecombat.com/play/level/ #{ session . levelID } ?team= #{ session . team } &session= #{ session . _id } &opponent= #{ match . opponents [ 0 ] . sessionID } "
2014-03-08 21:49:09 -05:00
onFetchedDefeatedOpponent = (err, defeatedOpponent) ->
if err
log . error " Couldn ' t find defeateded opponent: #{ err } "
defeatedOpponent = null
2014-06-30 22:16:26 -04:00
victoryContext = { opponent_name: defeatedOpponent ? . name ? ' Anoner ' , url: urlForMatch ( victory ) } if victory
2014-03-08 21:49:09 -05:00
onFetchedVictoriousOpponent = (err, victoriousOpponent) ->
if err
log . error " Couldn ' t find victorious opponent: #{ err } "
victoriousOpponent = null
2014-06-30 22:16:26 -04:00
defeatContext = { opponent_name: victoriousOpponent ? . name ? ' Anoner ' , url: urlForMatch ( defeat ) } if defeat
2014-08-30 18:19:42 -04:00
Level . find ( { original: session . level . original , created: { $gt: session . submitDate } } ) . select ( ' created commitMessage version ' ) . sort ( ' -created ' ) . lean ( ) . exec (err, levelVersions) ->
sendEmail defeatContext , victoryContext , ( if levelVersions . length then levelVersions else null )
2014-03-08 21:49:09 -05:00
if defeat
2014-06-30 22:16:26 -04:00
User . findOne ( { _id: defeat . opponents [ 0 ] . userID } ) . select ( ' name ' ) . lean ( ) . exec onFetchedVictoriousOpponent
2014-03-08 21:49:09 -05:00
else
onFetchedVictoriousOpponent null , null
if victory
2014-06-30 22:16:26 -04:00
User . findOne ( { _id: victory . opponents [ 0 ] . userID } ) . select ( ' name ' ) . lean ( ) . exec onFetchedDefeatedOpponent
2014-03-08 21:49:09 -05:00
else
onFetchedDefeatedOpponent null , null
2014-01-24 16:14:31 -05:00
2014-03-11 01:03:33 -04:00
getScoreHistoryGraphURL = (session, daysAgo) ->
# Totally duplicated in My Matches tab for now until we figure out what we're doing.
since = new Date ( ) - 86400 * 1000 * daysAgo
scoreHistory = ( s for s in session . scoreHistory ? [ ] when s [ 0 ] >= since )
return ' ' unless scoreHistory . length > 1
2014-03-13 22:20:22 -04:00
scoreHistory = _ . last scoreHistory , 100 # Chart URL needs to be under 2048 characters for GET
2014-03-11 01:03:33 -04:00
times = ( s [ 0 ] for s in scoreHistory )
times = ( ( 100 * ( t - times [ 0 ] ) / ( times [ times . length - 1 ] - times [ 0 ] ) ) . toFixed ( 1 ) for t in times )
scores = ( s [ 1 ] for s in scoreHistory )
2014-03-20 18:40:02 -04:00
lowest = _ . min scores #.concat([0])
highest = _ . max scores #.concat(50)
2014-03-11 01:03:33 -04:00
scores = ( Math . round ( 100 * ( s - lowest ) / ( highest - lowest ) ) for s in scores )
currentScore = Math . round scoreHistory [ scoreHistory . length - 1 ] [ 1 ] * 100
2014-03-13 22:20:22 -04:00
minScore = Math . round ( 100 * lowest )
maxScore = Math . round ( 100 * highest )
2014-03-11 01:03:33 -04:00
chartData = times . join ( ' , ' ) + ' | ' + scores . join ( ' , ' )
2014-03-13 22:20:22 -04:00
" https://chart.googleapis.com/chart?chs=600x75&cht=lxy&chtt=Score%3A+ #{ currentScore } &chts=222222,12,r&chf=a,s,000000FF&chls=2&chd=t: #{ chartData } &chxt=y&chxr=0, #{ minScore } , #{ maxScore } "
2014-01-24 16:14:31 -05:00
2014-07-16 15:13:21 -04:00
### End Ladder Update Email ###
2014-03-08 21:49:09 -05:00
handleMailchimpWebHook = (req, res) ->
post = req . body
unless post . type in [ ' unsubscribe ' , ' profile ' ]
res . send ' Bad post type '
return res . end ( )
unless post . data . email
res . send ' No email provided '
return res . end ( )
2014-06-30 22:16:26 -04:00
query = { ' mailChimp.leid ' : post . data . web_id }
2014-03-08 21:49:09 -05:00
User . findOne query , (err, user) ->
return errors . serverError ( res ) if err
if not user
return errors . notFound ( res )
handleProfileUpdate ( user , post ) if post . type is ' profile '
handleUnsubscribe ( user ) if post . type is ' unsubscribe '
user.updatedMailChimp = true # so as not to echo back to mailchimp
user . save (err) ->
return errors . serverError ( res ) if err
res . end ( ' Success ' )
2014-01-24 15:23:14 -05:00
2014-04-21 19:15:23 -04:00
module.exports.handleProfileUpdate = handleProfileUpdate = (user, post) ->
mailchimpSubs = post . data . merges . INTERESTS . split ( ' , ' )
2014-01-24 15:23:14 -05:00
2014-04-21 19:15:23 -04:00
for [ mailchimpEmailGroup , emailGroup ] in _ . zip ( mail . MAILCHIMP_GROUPS , mail . NEWS_GROUPS )
user . setEmailSubscription emailGroup , mailchimpEmailGroup in mailchimpSubs
2014-03-08 21:49:09 -05:00
2014-01-24 20:19:20 -05:00
fname = post . data . merges . FNAME
user . set ( ' firstName ' , fname ) if fname
lname = post . data . merges . LNAME
user . set ( ' lastName ' , lname ) if lname
2014-03-08 21:49:09 -05:00
2014-01-24 20:35:34 -05:00
user . set ' mailChimp.email ' , post . data . email
user . set ' mailChimp.euid ' , post . data . id
2014-03-08 21:49:09 -05:00
2014-04-21 19:15:23 -04:00
module.exports.handleUnsubscribe = handleUnsubscribe = (user) ->
2014-01-24 15:23:14 -05:00
user . set ' emailSubscriptions ' , [ ]
2014-04-21 19:15:23 -04:00
for emailGroup in mail . NEWS_GROUPS
user . setEmailSubscription emailGroup , false