Skip to content

Commit

Permalink
Feature/185 top upcoming games (winsleague#206)
Browse files Browse the repository at this point in the history
* renaming

* renaming

* initial framework and top picks calculator

* tests for top picks interest calculator

* fix home page test. need to figure out why this passed CI!

* fix test

* logging

* added email template and route

* some refactoring plus owners close in standings calculator

* don't use close in standings if there aren't enough games played

* initial league team recent wins calculator

* remove logging

* tweaked numbers

* renaming and more tests

* refactored email template sending

* fix template reference

* tweak ratings algo

* tweaked send time so it's farther from upcoming games mail

* test fix

* test fix

* test fix

* fix tests

* release spy

* better date formatting
  • Loading branch information
noahsw authored Aug 18, 2016
1 parent d309466 commit e812975
Show file tree
Hide file tree
Showing 21 changed files with 729 additions and 48 deletions.
28 changes: 28 additions & 0 deletions app/imports/api/emails/server/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Meteor } from 'meteor/meteor';
import { _ } from 'lodash';

import { PoolTeams } from '../../pool_teams/pool_teams';

export default {
getPlayerEmails(poolId, seasonId) {
// each userId can have multiple emails
// each email has an address property and a verified property
// return in the format 'name <[email protected]>, name <[email protected]>'

const players = PoolTeams.find({ poolId, seasonId })
.map(poolTeam => {
return {
_id: poolTeam.userId,
teamName: poolTeam.userTeamName,
};
});

const emailArray = _.flatten(players.map(player => {
return _.flatten(Meteor.users.find(player._id)
.map(user => user.emails))
.map(email => `${player.teamName} <${email.address}>`);
}));

return emailArray.join(', ');
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@
/* eslint-disable func-names, prefer-arrow-callback */

import { Factory } from 'meteor/dburles:factory';
import faker from 'faker';
import log from '../../../utils/log';

import WeeklyReport from './weekly-report';
import Common from './common';

import '../../pool_teams/pool_teams'; // needed for factory

import { assert } from 'chai';

describe('Weekly Report', () => {
describe('Email > Common', () => {
it('puts together player emails', () => {
const firstUser = Factory.create('user');

Expand All @@ -31,7 +30,7 @@ describe('Weekly Report', () => {
userId: secondUser._id,
});

const playerEmails = WeeklyReport.getPlayerEmails(poolId, seasonId);
const playerEmails = Common.getPlayerEmails(poolId, seasonId);

assert.equal(playerEmails, `first user <${firstUser.emails[0].address}>, second user <${secondUser.emails[0].address}>`);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,47 @@
import log from '../../../utils/log';
import { _ } from 'lodash';
import { Mailer } from 'meteor/lookback:emails';

import log from '../../../utils/log';

import Common from './common';
import { Seasons } from '../../seasons/seasons';
import { Pools } from '../../pools/pools';
import { PoolTeams } from '../../pool_teams/pool_teams';

export default {
emailReports() {
log.info('Sending out weekly email report');
sendAll() {
log.info('Sending out weekly leaderboard email');

const seasons = this.findActiveSeasons();
seasons.forEach(season => {
const poolIds = this.findEligiblePoolIds(season._id);
poolIds.forEach(poolId => {
this.emailReport(poolId, season._id);
this.sendIndividual(poolId, season._id);
});
});
},

emailReport(poolId, seasonId) {
log.info(`Emailing weekly report to pool ${poolId} for season ${seasonId}`);
sendIndividual(poolId, seasonId) {
log.info(`Emailing weekly leaderboard report to pool ${poolId}`);

const pool = Pools.findOne(poolId);
const poolName = pool.name;
const poolTeams = PoolTeams.find({ poolId, seasonId },
{ sort: { totalWins: -1, totalPlusMinus: -1 } });
const poolTeams = PoolTeams.find({
poolId,
seasonId,
}, {
sort: {
totalWins: -1,
totalPlusMinus: -1,
},
});

const playerEmails = this.getPlayerEmails(poolId, seasonId);
const playerEmails = Common.getPlayerEmails(poolId, seasonId);

Mailer.send({
to: playerEmails,
subject: `Wins Leaderboard for ${poolName}`,
template: 'weeklyEmail',
template: 'weeklyLeaderboardTemplate',
data: {
poolId,
seasonId,
Expand Down Expand Up @@ -70,26 +79,4 @@ export default {
]);
return poolAggregation.map(result => result._id);
},

getPlayerEmails(poolId, seasonId) {
// each userId can have multiple emails
// each email has an address property and a verified property
// return in the format 'name <[email protected]>, name <[email protected]>'

const players = PoolTeams.find({ poolId, seasonId })
.map(poolTeam => {
return {
_id: poolTeam.userId,
teamName: poolTeam.userTeamName,
};
});

const emailArray = _.flatten(players.map(player => {
return _.flatten(Meteor.users.find(player._id)
.map(user => user.emails))
.map(email => `${player.teamName} <${email.address}>`);
}));

return emailArray.join(', ');
},
};
91 changes: 91 additions & 0 deletions app/imports/api/emails/server/weekly-top-upcoming-games.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Mailer } from 'meteor/lookback:emails';
import moment from 'moment';

import log from '../../../utils/log';

import RatingCalculator from '../../pool_game_interest_ratings/server/calculator';
import Common from './common';
import LeagueFinder from '../../leagues/finder';
import { Games } from '../../games/games';
import { Pools } from '../../pools/pools';
import { PoolGameInterestRatings } from '../../pool_game_interest_ratings/pool_game_interest_ratings';

export default {
sendAll() {
log.info('Sending out weekly top upcoming games email');

this.calculateAllInterestRatings();

this.nflPools().forEach(pool => {
this.sendPoolEmail(pool);
});
},

calculateAllInterestRatings() {
this.nflPools().forEach(pool => {
this.calculatePoolInterestRatings(pool);
});
},

calculatePoolInterestRatings(pool) {
this.upcomingGames().forEach(game => {
RatingCalculator.calculate(pool, game);
});
},

upcomingGames() {
const leagueId = LeagueFinder.getIdByName('NFL');

const startDate = moment().toDate();
const endDate = moment().add(7, 'days').toDate();

log.info(`Upcoming games from ${startDate} to ${endDate}`);

return Games.find({
leagueId,
gameDate: {
$gte: startDate,
$lte: endDate,
},
});
},

nflPools() {
const leagueId = LeagueFinder.getIdByName('NFL');

return Pools.find({ leagueId });
},

sendPoolEmail(pool) {
log.info(`Emailing top upcoming games email to pool ${pool._id} for season ${pool.latestSeasonId}`);

const poolId = pool._id;
const poolName = pool.name;
const poolGameInterestRatings = PoolGameInterestRatings.find(
{
poolId: pool._id,
rating: {
$gte: 50,
},
},
{
sort: {
rating: -1,
},
limit: 5,
});

const playerEmails = this.getPlayerEmails(poolId, pool.latestSeasonId);

Mailer.send({
to: playerEmails,
subject: `Top Upcoming Games for ${poolName}`,
template: 'weeklyTopUpcomingGamesTemplate',
data: {
poolId,
poolName,
poolGameInterestRatings,
},
});
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Mongo } from 'meteor/mongo';
import { SimpleSchema } from 'meteor/aldeed:simple-schema';
import { Factory } from 'meteor/dburles:factory';
import { Random } from 'meteor/random';
import faker from 'faker';
import moment from 'moment-timezone';
import log from '../../utils/log';

import { Games } from '../games/games';

export const PoolGameInterestRatings = new Mongo.Collection('pool_game_interest_ratings');

PoolGameInterestRatings.schema = new SimpleSchema({
poolId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
},

gameId: {
type: String,
regEx: SimpleSchema.RegEx.Id,
},

rating: {
type: Number,
},

gameTitle: {
type: String,
},

justification: {
type: String,
},

createdAt: {
// Force value to be current date (on server) upon insert
// and prevent updates thereafter.
type: Date,
autoValue() {
if (this.isInsert) {
return new Date();
} else if (this.isUpsert) {
return { $setOnInsert: new Date() };
}
this.unset(); // Prevent user from supplying their own value
},
},

updatedAt: {
// Force value to be current date (on server) upon update
// and don't allow it to be set upon insert.
type: Date,
autoValue() {
if (this.isUpdate) {
return new Date();
}
},
denyInsert: true,
optional: true,
},
});

PoolGameInterestRatings.attachSchema(PoolGameInterestRatings.schema);

PoolGameInterestRatings.helpers({
gameTime() {
const game = Games.findOne(this.gameId);
const date = moment(game.gameDate).format('ddd,');
const est = moment(game.gameDate).tz('US/Eastern').format('ha zz');
const pst = moment(game.gameDate).tz('US/Pacific').format('ha zz');
return `${date} ${est}/${pst}`;
},
});

// Deny all client-side updates since we will be using methods to manage this collection
PoolGameInterestRatings.deny({
insert() { return true; },
update() { return true; },
remove() { return true; },
});

Factory.define('pool_game_interest_rating', PoolGameInterestRatings, {
// TODO
}).after(factory => {
log.debug('pool game interest rating factory created:', factory);
});
Loading

0 comments on commit e812975

Please sign in to comment.