codecombat/server/routes/mail.coffee

213 lines
9.2 KiB
CoffeeScript
Raw Normal View History

2014-01-24 14:48:00 -05:00
mail = require '../commons/mail'
map = _.invert mail.MAILCHIMP_GROUP_MAP
User = require '../users/User.coffee'
errors = require '../commons/errors'
2014-01-24 20:42:47 -05:00
#request = require 'request'
config = require '../../server_config'
2014-03-08 21:49:09 -05:00
LevelSession = require '../levels/sessions/LevelSession.coffee'
Level = require '../levels/Level.coffee'
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
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
getAllLadderScores = (next) ->
query = Level.find({type: 'ladder'})
.select('levelID')
.lean()
query.exec (err, levels) ->
if err
log.error "Couldn't fetch ladder levels. Error: ", err
return next []
for level in levels
for team in ['humans', 'ogres']
'I ... am not doing this.'
# Query to get sessions to make histogram
# db.level.sessions.find({"submitted":true,"levelID":"brawlwood",team:"ogres"},{"_id":0,"totalScore":1})
2014-03-12 11:11:48 -04:00
isRequestFromDesignatedCronHandler = (req, res) ->
if req.ip isnt config.mail.cronHandlerPublicIP and req.ip isnt config.mail.cronHandlerPrivateIP
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.")
res.end()
2014-03-13 19:11:44 -04:00
return false
return true
2014-03-08 21:49:09 -05:00
handleLadderUpdate = (req, res) ->
log.info("Going to see about sending ladder update emails.")
2014-03-12 11:11:48 -04:00
requestIsFromDesignatedCronHandler = isRequestFromDesignatedCronHandler req, res
unless requestIsFromDesignatedCronHandler then return
2014-03-09 00:29:41 -05:00
res.send('Great work, Captain Cron! I can take it from here.')
res.end()
# TODO: somehow fetch the histograms
2014-03-08 21:49:09 -05:00
emailDays = [1, 2, 4, 7, 30]
now = new Date()
getTimeFromDaysAgo = (daysAgo) ->
# 2 hours before the date
t = now - (86400 * daysAgo + 2 * 3600) * 1000
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-08 21:49:09 -05:00
startTime = getTimeFromDaysAgo daysAgo
endTime = startTime + 5 * 60 * 1000
#endTime = startTime + 1.5 * 60 * 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
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}\nError: #{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-09 00:29:41 -05:00
sendLadderUpdateEmail result, daysAgo for result in results
2014-03-08 21:49:09 -05:00
sendLadderUpdateEmail = (session, daysAgo) ->
User.findOne({_id: session.creator}).select("name email firstName lastName emailSubscriptions preferredLanguage").lean().exec (err, user) ->
if err
2014-03-10 16:20:00 -04:00
log.error "Couldn't find user for #{session.creator} from session #{session._id}"
return
unless user.email and ('notification' in user.emailSubscriptions) and not session.unsubscribed
log.info "Not sending email to #{user.email} #{user.name} because they only want emails about #{user.emailSubscriptions} - session unsubscribed: #{session.unsubscribed}"
2014-03-08 21:49:09 -05:00
return
unless session.levelName
log.info "Not sending email to #{user.email} #{user.name} because the session had no levelName in it."
return
name = if user.firstName and user.lastName then "#{user.firstName}" else user.name
2014-03-08 21:49:09 -05:00
name = "Wizard" if not name or name is "Anoner"
# 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.)
matches = _.filter session.matches, (match) -> match.date >= (new Date() - 86400 * 1000 * daysAgo)
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
defeat = _.last defeats
victory = _.last victories
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:
address: user.email
#address: 'nick@codecombat.com' # Debugging
2014-03-09 00:29:41 -05:00
name: name
email_data:
name: name
days_ago: daysAgo
wins: victories.length
losses: defeats.length
2014-03-09 00:29:41 -05:00
total_score: Math.round(session.totalScore * 100)
team: session.team
team_name: session.team[0].toUpperCase() + session.team.substr(1)
2014-03-09 00:29:41 -05:00
level_name: session.levelName
session_id: session._id
2014-03-09 00:29:41 -05:00
ladder_url: "http://codecombat.com/play/ladder/#{session.levelID}#my-matches"
score_history_graph_url: getScoreHistoryGraphURL session, daysAgo
2014-03-09 00:29:41 -05:00
defeat: defeatContext
victory: victoryContext
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
victoryContext = {opponent_name: defeatedOpponent?.name ? "Anoner", url: urlForMatch(victory)} if victory
onFetchedVictoriousOpponent = (err, victoriousOpponent) ->
if err
log.error "Couldn't find victorious opponent: #{err}"
victoriousOpponent = null
defeatContext = {opponent_name: victoriousOpponent?.name ? "Anoner", url: urlForMatch(defeat)} if defeat
sendEmail defeatContext, victoryContext
if defeat
User.findOne({_id: defeat.opponents[0].userID}).select("name").lean().exec onFetchedVictoriousOpponent
else
onFetchedVictoriousOpponent null, null
if victory
User.findOne({_id: victory.opponents[0].userID}).select("name").lean().exec onFetchedDefeatedOpponent
else
onFetchedDefeatedOpponent null, null
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
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)
lowest = _.min scores
highest = _.max scores
scores = (Math.round(100 * (s - lowest) / (highest - lowest)) for s in scores)
currentScore = Math.round scoreHistory[scoreHistory.length - 1][1] * 100
chartData = times.join(',') + '|' + scores.join(',')
"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}"
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()
query = {'mailChimp.leid':post.data.web_id}
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-01-24 15:39:05 -05:00
handleProfileUpdate = (user, post) ->
groups = post.data.merges.INTERESTS.split(', ')
2014-01-24 15:23:14 -05:00
groups = (map[g] for g in groups when map[g])
2014-01-24 20:19:20 -05:00
otherSubscriptions = (g for g in user.get('emailSubscriptions') when not mail.MAILCHIMP_GROUP_MAP[g])
groups = groups.concat otherSubscriptions
2014-01-24 15:23:14 -05:00
user.set 'emailSubscriptions', groups
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-01-24 15:39:05 -05:00
handleUnsubscribe = (user) ->
2014-01-24 15:23:14 -05:00
user.set 'emailSubscriptions', []
2014-03-08 21:49:09 -05:00
# badLog("Unsubscribing user object to: #{JSON.stringify(user.toObject(), null, '\t')}")