Comment all the things!

This commit is contained in:
2020-11-08 12:34:50 -06:00
parent 72f3923866
commit 06515b0559
73 changed files with 269 additions and 162 deletions

View File

@@ -4,8 +4,11 @@ const path = require('path')
/**
* FrontendUnit
* @extends Unit
* ----------------------------------------------------------------------------------------
* A Flitter application unit which sets up routes for accessing the javascript front-end
* pages we created in Project 3.
*
* @extends Unit
*/
class FrontendUnit extends Unit {
static get services() {
@@ -23,9 +26,8 @@ class FrontendUnit extends Unit {
}
/**
*
* @param app
*
* Initializes the unit. Creates the `/app` static endpoing and default settings.
* @param {FlitterApp} app
*/
async go(app) {
app.express.use('/app', [

View File

@@ -18,7 +18,7 @@ class GenerateMatchupsForWeekPatch extends Injectable {
}
/**
*
* Run the patch.
*/
async run() {
const Team = this.models.get('Team')
@@ -116,9 +116,9 @@ class GenerateMatchupsForWeekPatch extends Injectable {
}
/**
*
* @param team
* @returns data to represent what teams have been played by the param team
* Get a list of all teams played by the given team.
* @param {Team} team
* @returns {Promise<Array<string>>}
*/
async get_teams_played_by_team(team) {
const Matchup = this.models.get('Matchup')

View File

@@ -2,8 +2,11 @@ const { Injectable } = require('flitter-di')
/**
* GenerateWeeklyResultsPatch
* @extends Injectable
* ----------------------------------------------------------------------------
* A patch which generates the weekly team results using the player stat data
* for teh currently configured play week.
*
* @extends Injectable
*/
class GenerateWeeklyResultsPatch extends Injectable {
static get services() {

View File

@@ -1,8 +1,12 @@
const { Controller } = require('libflitter')
/**
* DraftBoard controller
* @extends Controller
* ------------------------------------------------------------------------
* This controller contains logic for handling API requests related to fetching
* and drafting available players. Its methods should handle Express requests &
* responses.
*
* @extends Controller
*/
class DraftBoard extends Controller {
static get services() {
@@ -42,6 +46,7 @@ class DraftBoard extends Controller {
.api()
}
// look up the player specified in the request
const Player = this.models.get('Player')
const player = await Player.findById(req.body.player_id)
if ( !player ) {
@@ -50,6 +55,7 @@ class DraftBoard extends Controller {
.api()
}
// Don't allow drafting already-drafted players
if ( await player.is_obligated() ) {
return res.status(400)
.message('This player has already been drafted.')

View File

@@ -1,18 +1,22 @@
const { Controller } = require('libflitter')
/*
/**
* Home Controller
* -------------------------------------------------------------
* Controller for the main homepage of this Flitter app. Methods here
* are used as handlers for routes specified in the route files.
*
* @extends Controller
*/
class Home extends Controller {
static get services() {
return [...super.services, 'sports_data']
}
/*
/**
* Serve the main welcome page.
* @param req
* @param res
*/
welcome(req, res){
if ( req.user ) {
@@ -22,6 +26,14 @@ class Home extends Controller {
}
}
/**
* Return the current session's status (including team information and
* information about the current stage of gameplay).
* @param req
* @param res
* @param next
* @return {Promise<*>}
*/
async get_status(req, res, next) {
return res.api({
team_id: req.user_team.id,

View File

@@ -1,8 +1,12 @@
const { Controller } = require('libflitter')
/**
* ScoresController
* @extends Controller
* ----------------------------------------------------------------------
* This controller contains logic for handling API requests related to the
* weekly scores and matchups endpoints.
*
* @extends Controller
*/
class ScoresController extends Controller {
static get services() {
@@ -22,6 +26,7 @@ class ScoresController extends Controller {
const current_week = await this.sports_data.current_play_week()
const weekly_data = []
// Convert all of the matchup instances to API format for each week
for ( let i = 1; i <= current_week; i += 1 ) {
const matchups = await Matchup.find({ week_num: i })
const api_data = await Promise.all(matchups.map(x => x.to_api()))
@@ -43,12 +48,14 @@ class ScoresController extends Controller {
const all_teams = await Team.find()
const stat_records = []
// Generate the cumulative team data for all teams
for ( const team of all_teams ) {
const rec = await team.cumulative_data()
rec.team_name = team.team_name
stat_records.push(rec)
}
// Sort the teams by number of wins, then number of points scored
stat_records.sort((a, b) => {
if ( a.wins === b.wins ) {
return a.points_scored - b.points_scored
@@ -57,6 +64,7 @@ class ScoresController extends Controller {
return a.wins > b.wins ? 1 : -1
})
// Return the records in a format compatible with the front-end
return res.api(stat_records.map((x, i) => {
return {
standing: {

View File

@@ -1,8 +1,12 @@
const { Controller } = require('libflitter')
/*
/**
* Teams Controller
* -------------------------------------------------------------
* This controller contains logic related to viewing and managing
* the user's team, team lineups, and team players.
*
* @extends Controller
*/
class Teams extends Controller {
static get services() {
@@ -78,10 +82,12 @@ class Teams extends Controller {
.api()
}
// fetch the team players & the current lineup
const player_ids = (await req.user_team.players()).map(x => x.id)
const lineup = await req.user_team.lineup()
lineup.clear_lineup()
// Add all the starting players to the lineup
for ( const player of req.body.starting_players ) {
if ( !player.id || !player.position ) continue;
@@ -90,6 +96,7 @@ class Teams extends Controller {
position: player.position,
}
// Don't allow adding other teams' players to the lineup
if ( !player_ids.includes(lineup_record.player_id) ) {
return res.status(400)
.message(`Sorry, the player ${lineup_record.player_id} is not on your team.`)
@@ -99,6 +106,7 @@ class Teams extends Controller {
lineup.start_player(lineup_record)
}
// Bench all the other players
for ( const player of req.body.benched_players ) {
if ( !player.id ) continue;
@@ -111,18 +119,12 @@ class Teams extends Controller {
lineup.bench_player(player)
}
console.log('pre save', lineup)
// Save the partial lineup
await lineup.save()
console.log('post save', lineup)
// Fetch a fresh version to fill in any missing players
const corrected_lineup = await req.user_team.lineup()
console.log('corrected', corrected_lineup)
return res.api(await corrected_lineup.to_api())
}

View File

@@ -4,6 +4,8 @@ const FormController = require('flitter-auth/controllers/Forms')
* Handles views and processing for auth registration/login/logout/etc.
* Most handlers are inherited from the default flitter-auth/controllers/Forms
* controller, however you can override them here as you need.
*
* This file was auto-generated by the Flitter framework.
*/
class Forms extends FormController {

View File

@@ -8,6 +8,8 @@ const Controller = require('flitter-auth/controllers/KeyAction')
* one-time links that call methods on controllers and (optionally)
* can even automatically sign in a user for the request, then log
* them out. e.g. a password reset link could use a key action.
*
* This file was auto-generated by the Flitter framework.
*/
class KeyAction extends Controller {

View File

@@ -5,6 +5,8 @@ const Oauth2Controller = require('flitter-auth/controllers/Oauth2')
* built-in OAuth2 server, if it is enabled. Most handlers are inherited
* from flitter-auth/controllers/Oauth2, but you can override them here
* as you need.
*
* This file was auto-generated by the Flitter framework.
*/
class Oauth2 extends Oauth2Controller {

View File

@@ -1,30 +0,0 @@
const { Model } = require('flitter-orm')
/*
* Example Model
* -------------------------------------------------------------
* This is a sample model. The schema or structure of the model should
* be specified here. It is then passed to flitter-orm and can be accessed
* globally using the canonical models service.
*/
class Example extends Model {
static get services() {
return [...super.services, 'output']
}
/*
* Define the flitter-orm schema of the model.
*/
static get schema() {
return {
name: String,
create_date: {type: Date, default: () => new Date},
}
}
log_name() {
this.output.info(`[Example Model] ${this.name}`)
}
}
module.exports = exports = Example

View File

@@ -32,6 +32,10 @@ class Lineup extends Model {
}
}
/**
* Calculate the fantasy points scored by the starting players on this lineup.
* @return {Promise<number>}
*/
async calculate_fantasy_points() {
const starting_players = await this.players_in_starting()
let points = 0

View File

@@ -1,8 +1,10 @@
const { Model } = require('flitter-orm')
/**
* Matchup
* @extends Model
* ---------------------------------------------------------------------------
* A model representing a single scheduled match-up between two teams.
*
* @extends Model
*/
class Matchup extends Model {
static get services() {
@@ -40,7 +42,8 @@ class Matchup extends Model {
}
/**
* updates the API's data
* Format this matchup to be compatible with the API output.
* @returns Promise<object>
*/
async to_api() {
const home_team = await this.home_team()

View File

@@ -1,22 +1,27 @@
const { Model } = require('flitter-orm')
const ActiveScope = require('./scopes/Active.scope')
/*
/**
* Player Model
* -------------------------------------------------------------
* A model representing a single player in the game.
*
* @extends Model
*/
class Player extends Model {
static get services() {
return [...super.services, 'output', 'models', 'sports_data']
}
// Enable soft-deletes using the active scope
static scopes = [new ActiveScope()]
/*
/**
* Define the flitter-orm schema of the model.
*/
static get schema() {
return {
// Data used by the patches internally, but not exposed to the API
patch_data: {
patch_team_id: Number,
patch_team_name: String,
@@ -24,6 +29,7 @@ class Player extends Model {
player_id: Number,
draft_position: Number,
},
player_number: Number,
first_name: String,
last_name: String,
@@ -38,6 +44,7 @@ class Player extends Model {
age: Number,
photo_url: String,
// Statistics pre-generated for the player to optimize performance
seed_stats: Object,
// False if the player doesn't have any week-1 stats.
@@ -74,7 +81,8 @@ class Player extends Model {
}
/**
* returns the id's of the unobligated players
* returns all of the unobligated players across all teams
* @return Promise<Array<Player>>
*/
static async get_unobligated_players() {
const Team = this.prototype.models.get('Team')
@@ -93,9 +101,9 @@ class Player extends Model {
}
/**
*
* @param week_num
* @returns the points scored of that week
* Returns the stats for the player for the given week.
* @param {number} week_num
* @returns Promise<WeeklyPlayerStat>
*/
async points_for_week(week_num) {
const WeeklyPlayerStat = this.models.get('WeeklyPlayerStat')
@@ -103,8 +111,8 @@ class Player extends Model {
}
/**
* @returns true if the player is obligated
* @returns false if the player is not obligates
* Determine whether the player belongs to a team or not.
* @returns {Promise<boolean>} - true if the player is obligated
*/
async is_obligated() {
const Team = this.models.get('Team')
@@ -117,12 +125,13 @@ class Player extends Model {
}
/**
*
* @param with_stats
* @returns updates the API's data
* Cast the player to a format compatible with the API.
* @param {boolean} [with_stats = false] - if true, look up the player's weekly stats
* @returns Promise<object>
*/
async to_api(with_stats = false) {
const stat = with_stats ? await this.points_for_week() : undefined
const current_week = await this.sports_data.current_play_week()
const stat = with_stats ? await this.points_for_week(current_week) : undefined
return {
id: this.id,

View File

@@ -1,15 +1,18 @@
const { Model } = require('flitter-orm')
/*
/**
* Team Model
* -------------------------------------------------------------
* A model representing a single team in the game.
*
* @extends Model
*/
class Team extends Model {
static get services() {
return [...super.services, 'output', 'models']
}
/*
/**
* Define the flitter-orm schema of the model.
*/
static get schema() {
@@ -52,6 +55,7 @@ class Team extends Model {
/**
* returns the lineup
* @return Promise<Lineup>
*/
async lineup() {
const Lineup = this.models.get('Lineup')
@@ -60,6 +64,7 @@ class Team extends Model {
/**
* Returns the players associated with the team.
* @return Promise<Array<Player>>
*/
async players() {
const Player = this.models.get('Player')
@@ -116,7 +121,8 @@ class Team extends Model {
}
/**
* updates the API's data
* Cast the team to the format expected for the API.
* @return Promise<object>
*/
async to_api() {
let user

View File

@@ -1,8 +1,10 @@
const { Model } = require('flitter-orm')
/**
* WeeklyPlayerStat model
* @extends Model
* -----------------------------------------------------------------------
* A record containing the statistics for a single player for a single week.
*
* @extends Model
*/
class WeeklyPlayerStat extends Model {
static get services() {
@@ -29,7 +31,8 @@ class WeeklyPlayerStat extends Model {
}
/**
* updates the API's data
* Cast the stats to a format expected by the API.
* @return Promise<object>
*/
async to_api() {
return {

View File

@@ -1,8 +1,10 @@
const { Model } = require('flitter-orm')
/**
* Weekly Team Stat model
* @extends Model
* ---------------------------------------------------------------------------
* A record containing the stats for a single team for a single lineup for a single week.
*
* @extends Model
*/
class WeeklyTeamStat extends Model {
static get services() {

View File

@@ -17,6 +17,8 @@ const Model = require('flitter-auth/model/KeyAction')
*
* See: module:flitter-auth/SecurityContext~SecurityContext#keyaction
* See: module:flitter-auth/model/KeyAction~KeyAction
*
* This file was automatically generated by the Flitter Framework.
*/
class KeyAction extends Model {

View File

@@ -1,9 +1,13 @@
const AuthUser = require('flitter-auth/model/User')
/*
/**
* Auth user model. This inherits fields and methods from the default
* flitter-auth/model/User model, however you can override methods and
* properties here as you need.
*
* This file was automatically generated by the Flitter Framework.
*
* @extends AuthUser
*/
class User extends AuthUser {
static get services() {
@@ -17,6 +21,11 @@ class User extends AuthUser {
}
// Other members and methods here
/**
* Get the team associated with this user.
* @return {Promise<Team>}
*/
async team() {
const Team = this.models.get('Team')
return Team.getForUser(this)

View File

@@ -1,6 +1,17 @@
const Scope = require('flitter-orm/src/model/Scope')
/**
* This is a model scope which excludes any models without is_active = true.
* In effect, this provides a mechanism for soft-deletes.
*
* @extends Scope
*/
class ActiveScope extends Scope {
/**
* Apply this scope's conditions to a model filter.
* @param to_filter
* @return {Promise<*>}
*/
async filter(to_filter) {
return to_filter.equal('is_active', true)
}

View File

@@ -7,6 +7,8 @@
*
* Route-specific middleware should be specified in the corresponding
* routes file.
*
* This file was automatically generated by the Flitter framework.
*/
const Middleware = [
"auth:Utility",

View File

@@ -1,18 +1,24 @@
const { Middleware } = require('libflitter')
/*
/**
* InjectUserTeam Middleware
* -------------------------------------------------------------
* For the authenticated user, looks up the associated Team instance
* and injects it as request.team.
* and injects it as request.user_team.
*
* @extends Middleware
*/
class InjectUserTeam extends Middleware {
static get services() {
return [...super.services, 'models']
}
/*
* Run the middleware test.
/**
* Inject the user's team into the request, or redirect to a login page.
* @param req
* @param res
* @param next
* @param [args = {}]
*/
async test(req, res, next, args = {}){
if ( !req.user ) return res.redirect('/auth/login')

View File

@@ -4,6 +4,8 @@
* Allows the request to proceed unless there's an authenticated user
* in the session. If so, redirect to the auth flow destination if one
* exists. If not, redirect to the default login route.
*
* This file was automatically generated by the Flitter framework.
*/
const Middleware = require('flitter-auth/middleware/GuestOnly')
class GuestOnly extends Middleware {

View File

@@ -4,6 +4,8 @@ const Middleware = require('flitter-auth/middleware/KeyAction')
* KeyAction Middleware
* -------------------------------------------------------------
* Middleware for processing key actions.
*
* This file was automatically generated by the Flitter framework.
*/
class KeyAction extends Middleware {

View File

@@ -3,6 +3,8 @@
* -------------------------------------------------------------
* Allows the request to proceed if a valid OAuth2 bearer token was
* provided. If not, return a JSON-encoded error message.
*
* This file was automatically generated by the Flitter framework.
*/
const Middleware = require('flitter-auth/middleware/Oauth2TokenOnly')
class Oauth2TokenOnly extends Middleware {

View File

@@ -3,6 +3,8 @@
* -------------------------------------------------------------
* Redirects the user to the login page if the registration page for
* a particular auth provider is not enabled.
*
* This file was automatically generated by the Flitter framework.
*/
const Middleware = require('flitter-auth/middleware/ProviderRegistrationEnabled')
class ProviderRegistrationEnabled extends Middleware {

View File

@@ -4,6 +4,8 @@
* Many auth routes specify the name of a particular auth provider to
* use. This middleware looks up the provider by that name and injects
* it into the request.
*
* This file was automatically generated by the Flitter framework.
*/
const Middleware = require('flitter-auth/middleware/ProviderRoute')
class ProviderRoute extends Middleware {

View File

@@ -4,6 +4,8 @@
* Allows the request to proceed if there's an authenticated user
* in the session. Otherwise, redirects the user to the login page
* of the default provider.
*
* This file was automatically generated by the Flitter framework.
*/
const Middleware = require('flitter-auth/middleware/UserOnly')
class UserOnly extends Middleware {

View File

@@ -4,6 +4,8 @@
* This should be applied globally. Ensures basic things about the
* request are true. For example, it provides the auth session data
* and handles auth flow.
*
* This file was automatically generated by the Flitter framework.
*/
const Middleware = require('flitter-auth/middleware/Utility')
class Utility extends Middleware {

View File

@@ -1,5 +1,7 @@
const Middleware = require('flitter-i18n/src/middleware/Localize')
/*
* This file was automatically generated by the Flitter framework.
*/
class LocalizeMiddleware extends Middleware {
}

View File

@@ -1,5 +1,8 @@
const Middleware = require('flitter-i18n/src/middleware/Scope')
/*
* This file was automatically generated by the Flitter framework.
*/
class ScopeMiddleware extends Middleware {
}

View File

@@ -16,6 +16,8 @@ const { Middleware } = require('libflitter')
*
* The 'value' attribute is optional. If none is provided, the request
* can proceed if the config value is truthy.
*
* This file was automatically generated by the Flitter framework.
*/
class Config extends Middleware {
static get services() {

View File

@@ -47,15 +47,25 @@ const index = {
'controller::Home.welcome'
],
// Get information about the user's team
'/my-team': ['controller::Teams.get_my_team'],
// Get a list of the user's team's players
'/my-team/players': ['controller::Teams.get_my_team_players'],
// Get the uesr's team's current lineup
'/my-team/lineup': ['controller::Teams.get_my_team_current_lineup'],
// Get a list of players available to be drafted
'/draft-board/available': ['controller::DraftBoard.get_available_players'],
// Get a list of matchup, grouped by week number
'/matchups': ['controller::Scores.get_weekly_scores'],
// Get the current league standings
'/league-standings': ['controller::Scores.get_league_standings'],
// Get the status of the current user's session, and game play
'/status': ['controller::Home.get_status'],
},
@@ -67,9 +77,13 @@ const index = {
* or middleware that are applied in order.
*/
post: {
// Save changes to the current user's team
'/my-team': ['controller::Teams.save_my_team'],
// Save the current user's team's lineup
'/my-team/lineup': ['controller::Teams.save_my_team_lineup'],
// Draft the given player to the current user's team
'/draft-board/draft-player': ['controller::DraftBoard.draft_player_to_team'],
},

View File

@@ -13,6 +13,8 @@
* You can omit the provider name to use the default provider:
*
* /auth/register
*
* This file was automatically generated by the Flitter framework.
*/
const index = {

View File

@@ -1,3 +1,7 @@
/*
* This file was automatically generated by the Flitter framework.
*/
module.exports = exports = {
prefix: '/auth/action', // This is assumed by flitter-auth. Don't change it.
middleware: [],

View File

@@ -2,6 +2,8 @@
* oauth2 Routes
* -------------------------------------------------------------
* Routes pertaining to the flitter-auth OAuth2 server implementation.
*
* This file was automatically generated by the Flitter framework.
*/
const oauth2 = {

View File

@@ -3,6 +3,8 @@
* -------------------------------------------------------------
* This is a sample routes file. Routes and their handlers should be
* defined here, but no logic should occur.
*
* This file was automatically generated by the Flitter framework.
*/
const index = {
@@ -47,24 +49,7 @@ const index = {
// Placeholder for auth dashboard. You'd replace this with
// your own route protected by 'middleware::auth:UserOnly'
'/dash': [ 'controller::Home.welcome' ],
'/api/list-all-teams': [
'controller::Teams.list_all_teams'
],
},
/*
* Define POST routes.
* These routes are registered as POST methods.
* Handlers for these routes should be specified as
* an array of canonical references to controller methods
* or middleware that are applied in order.
*/
post: {
'/api/create-team': [
'controller::Teams.create_team'
],
'/dash': ['controller::Home.welcome'],
},
// You can include other HTTP verbs here.

View File

@@ -3,39 +3,74 @@ const axios = require('axios').default;
/**
* A service class for interacting with data from the SportsDataIO API.
* @extends Service
*/
class SportsDataService extends Service {
static get services() {
return [...super.services, 'configs', 'models', 'utility']
}
/**
* Resolves true if the game is currently in the draft stage.
* @return {Promise<boolean>}
*/
async is_draft_stage() {
const Setting = this.models.get('models::setting')
return this.utility.infer(await Setting.get('in_draft_stage'))
}
/**
* Resolves to the current week number of gameplay.
* @return {Promise<number>}
*/
async current_play_week() {
const Setting = this.models.get('models::setting')
return this.utility.infer(await Setting.get('current_week'))
}
/**
* Fetches a list of all players on the given team from the sports data API.
* @param {string} team_key
* @return {Promise<Array<any>>}
*/
async get_team_players(team_key) {
return this.get_request(`Players/${team_key}`)
}
/**
* Fetches a list of all active teams from the sports data API.
* @return {Promise<Array<any>>}
*/
async get_active_teams() {
return this.get_request('Teams')
}
/**
* Make a get request to the sports data API.
* @param {string} path
* @param {string} [base = 'scores'] - the API domain (scores, projections, &c.)
* @return {Promise<any>}
*/
async get_request(path, base = 'scores') {
const response = await axios.get(this.url(path, base))
return response.data
}
/**
* Fetches a list of player stats for all players in the league for the given week.
* @param {number} week_num
* @return {Promise<Array<any>>>}
*/
async get_week_player_stats(week_num) {
return this.get_request(`PlayerGameProjectionStatsByWeek/${this.configs.get('server.sports_data.season')}/${week_num}`, 'projections')
}
/**
* Resolve an endpoint and an API domain to a fully-qualified URL to the sports data API.
* @param {string} path
* @param {string} [base = 'scores'] - the API domain (scores, projections, &c.)
* @return {string}
*/
url(path, base = 'scores') {
if ( path.startsWith('/') ) path = path.slice(1)
return `https://api.sportsdata.io/v3/nfl/${base}/json/${path}?key=${this.configs.get('server.sports_data.api_key')}`

View File

@@ -1,3 +1,4 @@
// this file was automatically generated by the Flitter framework
html
head
title #{title} | #{_app.name}

View File

@@ -1,3 +1,4 @@
// this file was automatically generated by the Flitter framework
extends ./auth_page
block content

View File

@@ -1,3 +1,4 @@
// this file was automatically generated by the Flitter framework
extends ./auth_page
block content

View File

@@ -1,3 +1,4 @@
// this file was automatically generated by the Flitter framework
extends ./form
block form

View File

@@ -1,3 +1,4 @@
// this file was automatically generated by the Flitter framework
extends ./auth_page
block content

View File

@@ -1,3 +1,4 @@
// this file was automatically generated by the Flitter framework
extends ./form
block form

View File

@@ -1,3 +1,4 @@
// this file was automatically generated by the Flitter framework
extends error
block head

View File

@@ -1,3 +1,4 @@
// this file was automatically generated by the Flitter framework
extends error
block head

View File

@@ -1,3 +1,4 @@
// this file was automatically generated by the Flitter framework
extends error
block head

View File

@@ -1,3 +1,4 @@
// this file was automatically generated by the Flitter framework
extends error
block head

View File

@@ -1,3 +1,4 @@
// this file was automatically generated by the Flitter framework
extends error
block head

View File

@@ -1,3 +1,4 @@
// this file was automatically generated by the Flitter framework
extends error
block head

View File

@@ -1,3 +1,4 @@
// this file was automatically generated by the Flitter framework
html
head
title Uh-Oh! | #{_app ? _app.name : 'Flitter'}

View File

@@ -1,3 +1,4 @@
// this file was automatically generated by the Flitter framework
html
head
block head

View File

@@ -1,3 +1,4 @@
// this file was automatically generated by the Flitter framework
html
head
title #{T('welcome')} | #{_app.name}