2014-01-24 14:48:00 -05:00
mail = require ' ../commons/mail '
2014-06-19 11:38:07 -04:00
User = require ' ../users/User '
2014-01-24 14:48:00 -05:00
errors = require ' ../commons/errors '
2014-01-24 20:42:47 -05:00
#request = require 'request'
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-01-24 14:48:00 -05:00
2014-01-24 20:42:47 -05:00
#badLog = (text) ->
# console.log text
# request.post 'http://requestb.in/1brdpaz1', { form: {log: text} }
2014-03-08 21:49:09 -05: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-08 19:26:51 -04:00
2014-03-09 21:46:11 -04:00
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-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-03-12 11:11:48 -04:00
requestIsFromDesignatedCronHandler = isRequestFromDesignatedCronHandler req , res
2014-03-22 12:48:36 -04:00
return unless requestIsFromDesignatedCronHandler or DEBUGGING
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-06-30 22:16:26 -04:00
selectString = ' creator team levelName levelID totalScore matches submitted submitDate scoreHistory '
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-03-22 12:48:36 -04:00
#ties = _.filter matches, (match) -> match.metrics.rank is 0 and match.opponents[0].metrics.rank is 0
2014-03-11 00:30:46 -04:00
defeat = _ . last defeats
victory = _ . last victories
2014-03-22 12:48:36 -04:00
#log.info "#{user.name} had #{matches.length} matches from last #{daysAgo} days out of #{session.matches.length} total matches. #{defeats.length} defeats, #{victories.length} victories, and #{ties.length} ties."
#matchInfos = ("\t#{match.date}\t#{match.date >= getTimeFromDaysAgo(now, daysAgo)}\t#{match.metrics.rank}\t#{match.opponents[0].metrics.rank}" for match in session.matches)
#log.info "Matches:\n#{matchInfos.join('\n')}"
2014-03-08 21:49:09 -05:00
sendEmail = (defeatContext, victoryContext) ->
# 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-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-03-08 21:49:09 -05:00
sendEmail defeatContext , victoryContext
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-03-08 21:49:09 -05:00
handleMailchimpWebHook = (req, res) ->
post = req . body
#badLog("Got post data: #{JSON.stringify(post, null, '\t')}")
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-01-24 20:42:47 -05:00
# badLog("Updating user object to: #{JSON.stringify(user.toObject(), null, '\t')}")
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
2014-01-24 16:14:31 -05:00
2014-03-08 21:49:09 -05:00
# badLog("Unsubscribing user object to: #{JSON.stringify(user.toObject(), null, '\t')}")