diff --git a/app/controllers/Teams.controller.js b/app/controllers/Teams.controller.js index 79f24e6..5146727 100644 --- a/app/controllers/Teams.controller.js +++ b/app/controllers/Teams.controller.js @@ -21,19 +21,50 @@ class Teams extends Controller { return res.api(await req.user_team.to_api()) } + /** + * Return the API data for the players on the current user's team. + * @param req + * @param res + * @param next + * @return {Promise<*>} + */ async get_my_team_players(req, res, next) { const players = await req.user_team.players() - console.log(players) - console.log(await players[0].to_api()) return res.api(await Promise.all(players.map(x => x.to_api()))) } + /** + * Return the API data for the current lineup for the current user's team. + * @param req + * @param res + * @param next + * @return {Promise<*>} + */ + async get_my_team_current_lineup(req, res, next) { + const Lineup = this.models.get('Lineup') + const lineup = await Lineup.get_and_update_for_team(req.user_team) + return res.api(await lineup.to_api()) + } + + /** + * Return the API data for a list of all teams. + * @param req + * @param res + * @return {Promise<*>} + */ async list_all_teams(req, res) { const TeamModel = this.models.get('Team') const teams = await TeamModel.find() return res.api(teams) } + /** + * API endpoint for creating a new team. + * @todo remove this - happens automatically per-user + * @param req + * @param res + * @return {Promise<*>} + */ async create_team(req, res) { const TeamModel = this.models.get('Team') diff --git a/app/models/Lineup.model.js b/app/models/Lineup.model.js new file mode 100644 index 0000000..33c5df4 --- /dev/null +++ b/app/models/Lineup.model.js @@ -0,0 +1,191 @@ +const { Model } = require('flitter-orm') + +/** + * Model representing a starting lineup/bench configuration for a + * given team. These will have copies frozen for each week that progresses. + * @extends Model + */ +class Lineup extends Model { + static get services() { + return [...super.services, 'models'] + } + + static get schema() { + return { + // Associated team ID + team_id: String, + + // Array of player IDs on the team on the bench + benched_player_ids: [String], + + // Array of player IDs on the team and their starting positions + starting_players: [{ + player_id: String, + position: String, + }], + + // If true, this is the current "draft" of the lineup + active: { type: Boolean, default: true }, + + // If not active, then it was archived for the week with this number + week_num: Number, + } + } + + /** + * Given a team, fetch the latest draft lineup for that team. + * This will also update the lineup record so that it includes all players + * currently on the team (benching any players not already in the lineup). + * + * If no lineup exists, one will be created. + * + * @param {Team} team + * @return {Promise} + */ + static async get_and_update_for_team(team) { + let lineup = await this.findOne({ team_id: team.id }) + + if ( !lineup ) { + lineup = new this({ + team_id: team.id, + benched_player_ids: [], + starting_players: [], + active: true, + }) + } else if ( !lineup.active ) { + lineup = new this({ + team_id: team.id, + benched_player_ids: lineup.benched_player_ids, + starting_players: lineup.starting_players, + active: true, + }) + } + + // Make sure all players on the team are either on the bench or in the lineup + const players = await team.players() + for ( const player of players ) { + if ( !lineup.has_player(player) ) { + lineup.bench_player(player) + } + } + + await lineup.save() + return lineup + } + + /** + * Returns an array of Players that are on the bench for this lineup. + * @return {Promise>} + */ + async players_on_bench() { + const Player = this.models.get('Player') + return Player.find({ + _id: { + $in: this.benched_player_ids.map(x => Player.to_object_id(x)), + }, + }) + } + + /** + * Returns an array of Players that are on the starting lineup. + * @return {Promise>} + */ + async players_in_starting() { + const Player = this.models.get('Player') + return Player.find({ + _id: { + $in: this.starting_players.map(x => Player.to_object_id(x.player_id)), + }, + }) + } + + /** + * Returns true if the given player is on the bench in this lineup. + * @param {Player} player + * @return {boolean} + */ + has_bench_player(player) { + return this.benched_player_ids.includes(player.id) + } + + /** + * Returns true if the given player is on the starting lineup in this lineup. + * @param {Player} player + * @return {boolean} + */ + has_starting_player(player) { + return !!this.starting_players.find(x => x.player_id === player.id) + } + + /** + * Returns true if the given player is in this lineup anywhere. + * @param {Player} player + * @return {boolean} + */ + has_player(player) { + return this.has_bench_player(player) || this.has_starting_player(player) + } + + /** + * Removes the player from the starting lineup if they are there and + * adds them to the bench if they aren't already there. + * @param {Player} player + */ + bench_player(player) { + this.starting_players = this.starting_players.filter(x => x.player_id !== player.id) + if ( !this.benched_player_ids.includes(player.id) ) { + this.benched_player_ids.push(player.id) + } + } + + /** + * Cast the lineup to an object which can be returned via the API. + * @return {Promise} + */ + async to_api() { + // positions to guarantee are in the line-up + let lineup_positions = ['QB', 'RB', 'RB', 'WR', 'WR', 'TE', 'FLX', 'DST'] + + const data = { + team_id: this.team_id, + active: this.active, + week_num: this.week_num, + } + + // build the starting players data + const starting_players = await this.players_in_starting() + const build_starting_players = [] + for ( const player of this.starting_players ) { + // Find the player instance and cast it to an API object + const player_inst = starting_players.find(x => x.id === player.player_id) + build_starting_players.push({ + position: player.position, + ...(await player_inst.to_api()) + }) + + // remove the position from the array of positions to back-fill + lineup_positions = lineup_positions.filter(x => x !== player.position) + } + + // Fill in any missing positions into the data + for ( const position of lineup_positions ) { + build_starting_players.push ({ position }) + } + + // build the benched players data + const bench_players = await this.players_on_bench() + const build_benched_players = [] + for ( const player of bench_players ) { + // Cast the starting player to an API object + const obj = await player.to_api() + obj.position = 'B' + build_benched_players.push(obj) + } + + data.starting_players = build_starting_players + data.benched_players = build_benched_players + return data + } +} + +module.exports = exports = Lineup diff --git a/app/routing/routers/api.routes.js b/app/routing/routers/api.routes.js index 17bcc0e..809a1e3 100644 --- a/app/routing/routers/api.routes.js +++ b/app/routing/routers/api.routes.js @@ -49,6 +49,7 @@ const index = { '/my-team': ['controller::Teams.get_my_team'], '/my-team/players': ['controller::Teams.get_my_team_players'], + '/my-team/lineup': ['controller::Teams.get_my_team_current_lineup'], }, /* diff --git a/frontend/src/components/pages/MyTeam.component.js b/frontend/src/components/pages/MyTeam.component.js index 8c7958d..dbc332b 100644 --- a/frontend/src/components/pages/MyTeam.component.js +++ b/frontend/src/components/pages/MyTeam.component.js @@ -68,32 +68,7 @@ class MyTeamComponent extends Component { * a position, then only the "postition" key will be set. * @type {object[]} */ - starting_players = [ - { - position: 'QB', - }, - { - position: 'RB', - }, - { - position: 'RB', - }, - { - position: 'WR', - }, - { - position: 'WR', - }, - { - position: 'TE', - }, - { - position: 'FLX', - }, - { - position: 'DST', - }, - ] + starting_players = [] /** * Players on the bench. @@ -112,16 +87,16 @@ class MyTeamComponent extends Component { }, { header: 'Player', - key: 'player_name', + key: 'name', type: GridCellRenderType.HTML, renderer: (_, data) => { - if ( !data.player_name ) { + if ( !data.name ) { return `none` } else { return `
- ${data.player_name} - ${data.player_name} + ${data.name} + ${data.name} (${data.number})
` } @@ -129,7 +104,7 @@ class MyTeamComponent extends Component { }, { header: '', - key: 'player_name', + key: 'name', type: GridCellRenderType.Component, component: Vue.component('app-action-button'), button_color: (row, col) => this.moving_player ? '#CC5746' : '#0582CA', @@ -137,24 +112,28 @@ class MyTeamComponent extends Component { return this.moving_player ? 'Here' : 'Move' }, button_hidden: (row, col) => { - if ( this.moving_player && this.moving_player.player_name !== row.player_name ) return false; - if ( !row.player_name ) return true; - return this.moving_player && this.moving_player.player_name === row.player_name; + if ( this.moving_player && this.moving_player.name !== row.name ) return false; + if ( !row.name ) return true; + return this.moving_player && this.moving_player.name === row.name; }, on_click: (row, col) => { if ( !this.moving_player ) { this.moving_player = row; } else { const old_row = {...row}; - row.player_name = this.moving_player.player_name; - row.ecr = this.moving_player.ecr; - row.image = this.moving_player.image; + const moved_row = {...this.moving_player}; + + for ( const prop in moved_row ) { + if ( prop === 'position' ) continue; + row[prop] = moved_row[prop] + } + + for ( const prop in moved_row ) { + if ( prop === 'position' ) continue; + this.moving_player[prop] = old_row[prop] + } - this.moving_player.player_name = old_row.player_name; - this.moving_player.ecr = old_row.ecr; - this.moving_player.image = old_row.image; this.moving_player = undefined; - console.log(this.moving_player, row); } this.$_vue_inst.update(); // $_vue_inst refers to the Vue.component instance, not the data class. @@ -201,11 +180,11 @@ class MyTeamComponent extends Component { */ async vue_on_create() { console.log('api', api) - const my_team = await api.get_my_team() + const [my_team, lineup] = await Promise.all([api.get_my_team(), api.get_my_team_current_lineup()]) this.team_name = my_team.team_name this.overall_data = await api.get_my_team_players() - - this.bench_players = this.overall_data.map(x => { x = {...x, position: 'B'}; return x }) + this.bench_players = lineup.benched_players + this.starting_players = lineup.starting_players setTimeout(() => { this.update(); diff --git a/frontend/src/module/api.js b/frontend/src/module/api.js index 2093171..b210408 100644 --- a/frontend/src/module/api.js +++ b/frontend/src/module/api.js @@ -11,6 +11,10 @@ class API { return this.get_request('my-team/players') } + async get_my_team_current_lineup() { + return this.get_request('my-team/lineup') + } + async get_request(...parts) { const url = this.build_url(...parts) const result = await fetch(url)