mail = require '../commons/mail' map = _.invert mail.MAILCHIMP_GROUP_MAP User = require '../users/User.coffee' errors = require '../commons/errors' #request = require 'request' config = require '../../server_config' LevelSession = require '../levels/sessions/LevelSession.coffee' Level = require '../levels/Level.coffee' log = require 'winston' sendwithus = require '../sendwithus' #badLog = (text) -> # console.log text # request.post 'http://requestb.in/1brdpaz1', { form: {log: text} } module.exports.setup = (app) -> 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}) 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() return false return true handleLadderUpdate = (req, res) -> log.info("Going to see about sending ladder update emails.") requestIsFromDesignatedCronHandler = isRequestFromDesignatedCronHandler req, res unless requestIsFromDesignatedCronHandler then return res.send('Great work, Captain Cron! I can take it from here.') res.end() # TODO: somehow fetch the histograms 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 # Get every session that was submitted in a 5-minute window after the time. startTime = getTimeFromDaysAgo daysAgo endTime = startTime + 5 * 60 * 1000 #endTime = startTime + 1.5 * 60 * 60 * 1000 # Debugging: make sure there's something to send findParameters = {submitted: true, submitDate: {$gt: new Date(startTime), $lte: new Date(endTime)}} # TODO: think about putting screenshots in the email selectString = "creator team levelName levelID totalScore matches submitted submitDate scoreHistory" query = LevelSession.find(findParameters) .select(selectString) .lean() do (daysAgo) -> query.exec (err, results) -> if err log.error "Couldn't fetch ladder updates for #{findParameters}\nError: #{err}" return errors.serverError res, "Ladder update email query failed: #{JSON.stringify(err)}" log.info "Found #{results.length} ladder sessions to email updates about for #{daysAgo} day(s) ago." sendLadderUpdateEmail result, daysAgo for result in results sendLadderUpdateEmail = (session, daysAgo) -> User.findOne({_id: session.creator}).select("name email firstName lastName emailSubscriptions preferredLanguage").lean().exec (err, user) -> if err 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}" 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 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 victories = _.filter matches, (match) -> match.metrics.rank is 0 and match.opponents[0].metrics.rank is 1 defeat = _.last defeats victory = _.last victories 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 name: name email_data: name: name days_ago: daysAgo wins: victories.length losses: defeats.length total_score: Math.round(session.totalScore * 100) team: session.team team_name: session.team[0].toUpperCase() + session.team.substr(1) level_name: session.levelName session_id: session._id ladder_url: "http://codecombat.com/play/ladder/#{session.levelID}#my-matches" score_history_graph_url: getScoreHistoryGraphURL session, daysAgo 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." sendwithus.api.send context, (err, result) -> log.error "Error sending ladder update email: #{err} with result #{result}" if err urlForMatch = (match) -> "http://codecombat.com/play/level/#{session.levelID}?team=#{session.team}&session=#{session._id}&opponent=#{match.opponents[0].sessionID}" 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}" 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') handleProfileUpdate = (user, post) -> groups = post.data.merges.INTERESTS.split(', ') groups = (map[g] for g in groups when map[g]) otherSubscriptions = (g for g in user.get('emailSubscriptions') when not mail.MAILCHIMP_GROUP_MAP[g]) groups = groups.concat otherSubscriptions user.set 'emailSubscriptions', groups fname = post.data.merges.FNAME user.set('firstName', fname) if fname lname = post.data.merges.LNAME user.set('lastName', lname) if lname user.set 'mailChimp.email', post.data.email user.set 'mailChimp.euid', post.data.id # badLog("Updating user object to: #{JSON.stringify(user.toObject(), null, '\t')}") handleUnsubscribe = (user) -> user.set 'emailSubscriptions', [] # badLog("Unsubscribing user object to: #{JSON.stringify(user.toObject(), null, '\t')}")