From 6a7dd764454a09d169eb26cd37b0faabf84c412a Mon Sep 17 00:00:00 2001 From: Patrick Fairbank Date: Fri, 9 Aug 2019 23:13:45 -0700 Subject: [PATCH] Add awards management and automatic lower third creation. --- TODO.md | 2 - .../20140811222034_CreateLowerThirds.sql | 3 +- db/migrations/20190809204907_CreateAwards.sql | 11 ++ model/award.go | 62 +++++++ model/award_test.go | 90 ++++++++++ model/database.go | 4 + model/lower_third.go | 14 ++ model/lower_third_test.go | 30 +++- templates/base.html | 1 + templates/setup_awards.html | 62 +++++++ tournament/awards.go | 168 ++++++++++++++++++ tournament/awards_test.go | 146 +++++++++++++++ web/match_play.go | 18 +- web/setup_awards.go | 75 ++++++++ web/setup_awards_test.go | 35 ++++ web/setup_lower_thirds.go | 1 + web/setup_lower_thirds_test.go | 14 +- web/web.go | 2 + 18 files changed, 725 insertions(+), 13 deletions(-) create mode 100644 db/migrations/20190809204907_CreateAwards.sql create mode 100644 model/award.go create mode 100644 model/award_test.go create mode 100644 templates/setup_awards.html create mode 100644 tournament/awards.go create mode 100644 tournament/awards_test.go create mode 100644 web/setup_awards.go create mode 100644 web/setup_awards_test.go diff --git a/TODO.md b/TODO.md index 87c8990..a753a3b 100644 --- a/TODO.md +++ b/TODO.md @@ -3,7 +3,6 @@ Cheesy Arena To-Do List ### Features for FRC parity * Event wizard to guide scorekeeper through running an event -* Awards tracking and publishing * Elimination bracket report and audience screen * Interface for viewing logs (right now it's CSV files in Excel) @@ -15,7 +14,6 @@ Cheesy Arena To-Do List ### Scorekeeper-facing features * Ability to unscore a match and reset it to non-played status * Allow reordering of sponsor slides in the setup page -* Automatic creation of lower thirds for awards ### Features for other volunteers * Referee interface: add timer starting at field reset to track time limit for calling timeouts/backups diff --git a/db/migrations/20140811222034_CreateLowerThirds.sql b/db/migrations/20140811222034_CreateLowerThirds.sql index e5daf34..e346b20 100644 --- a/db/migrations/20140811222034_CreateLowerThirds.sql +++ b/db/migrations/20140811222034_CreateLowerThirds.sql @@ -3,7 +3,8 @@ CREATE TABLE lower_thirds ( id INTEGER PRIMARY KEY, toptext VARCHAR(255), bottomtext VARCHAR(255), - displayorder int + displayorder int, + awardid int ); -- +goose Down diff --git a/db/migrations/20190809204907_CreateAwards.sql b/db/migrations/20190809204907_CreateAwards.sql new file mode 100644 index 0000000..03c6d4e --- /dev/null +++ b/db/migrations/20190809204907_CreateAwards.sql @@ -0,0 +1,11 @@ +-- +goose Up +CREATE TABLE awards ( + id INTEGER PRIMARY KEY, + type int, + awardname VARCHAR(255), + teamid int, + personname VARCHAR(255) +); + +-- +goose Down +DROP TABLE awards; diff --git a/model/award.go b/model/award.go new file mode 100644 index 0000000..97293e8 --- /dev/null +++ b/model/award.go @@ -0,0 +1,62 @@ +// Copyright 2019 Team 254. All Rights Reserved. +// Author: pat@patfairbank.com (Patrick Fairbank) +// +// Model and datastore CRUD methods for an award. + +package model + +type Award struct { + Id int + Type AwardType + AwardName string + TeamId int + PersonName string +} + +type AwardType int + +const ( + JudgedAward AwardType = iota + FinalistAward + WinnerAward +) + +func (database *Database) CreateAward(award *Award) error { + return database.awardMap.Insert(award) +} + +func (database *Database) GetAwardById(id int) (*Award, error) { + award := new(Award) + err := database.awardMap.Get(award, id) + if err != nil && err.Error() == "sql: no rows in result set" { + award = nil + err = nil + } + return award, err +} + +func (database *Database) SaveAward(award *Award) error { + _, err := database.awardMap.Update(award) + return err +} + +func (database *Database) DeleteAward(award *Award) error { + _, err := database.awardMap.Delete(award) + return err +} + +func (database *Database) TruncateAwards() error { + return database.awardMap.TruncateTables() +} + +func (database *Database) GetAllAwards() ([]Award, error) { + var awards []Award + err := database.awardMap.Select(&awards, "SELECT * FROM awards ORDER BY id") + return awards, err +} + +func (database *Database) GetAwardsByType(awardType AwardType) ([]Award, error) { + var awards []Award + err := database.awardMap.Select(&awards, "SELECT * FROM awards WHERE type = ? ORDER BY id", awardType) + return awards, err +} diff --git a/model/award_test.go b/model/award_test.go new file mode 100644 index 0000000..2e487cd --- /dev/null +++ b/model/award_test.go @@ -0,0 +1,90 @@ +// Copyright 2019 Team 254. All Rights Reserved. +// Author: pat@patfairbank.com (Patrick Fairbank) + +package model + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestGetNonexistentAward(t *testing.T) { + db := setupTestDb(t) + + award, err := db.GetAwardById(1114) + assert.Nil(t, err) + assert.Nil(t, award) +} + +func TestAwardCrud(t *testing.T) { + db := setupTestDb(t) + + award := Award{0, JudgedAward, "Saftey Award", 254, ""} + db.CreateAward(&award) + award2, err := db.GetAwardById(1) + assert.Nil(t, err) + assert.Equal(t, award, *award2) + + award2.AwardName = "Spirit Award" + db.CreateAward(award2) + awards, err := db.GetAllAwards() + assert.Nil(t, err) + if assert.Equal(t, 2, len(awards)) { + assert.Equal(t, award, awards[0]) + assert.Equal(t, *award2, awards[1]) + } + + award.TeamId = 0 + award.PersonName = "Travus Cubington" + db.SaveAward(&award) + award2, err = db.GetAwardById(1) + assert.Nil(t, err) + assert.Equal(t, award.TeamId, award2.TeamId) + assert.Equal(t, award.PersonName, award2.PersonName) + + db.DeleteAward(&award) + award2, err = db.GetAwardById(1) + assert.Nil(t, err) + assert.Nil(t, award2) +} + +func TestTruncateAwards(t *testing.T) { + db := setupTestDb(t) + + award := Award{0, JudgedAward, "Saftey Award", 254, ""} + db.CreateAward(&award) + db.TruncateAwards() + award2, err := db.GetAwardById(1) + assert.Nil(t, err) + assert.Nil(t, award2) +} + +func TestGetAwardsByType(t *testing.T) { + db := setupTestDb(t) + + award1 := Award{0, WinnerAward, "Event Winner", 1114, ""} + db.CreateAward(&award1) + award2 := Award{0, FinalistAward, "Event Finalist", 2056, ""} + db.CreateAward(&award2) + award3 := Award{0, JudgedAward, "Saftey Award", 254, ""} + db.CreateAward(&award3) + award4 := Award{0, WinnerAward, "Event Winner", 254, ""} + db.CreateAward(&award4) + + awards, err := db.GetAwardsByType(JudgedAward) + assert.Nil(t, err) + if assert.Equal(t, 1, len(awards)) { + assert.Equal(t, award3, awards[0]) + } + awards, err = db.GetAwardsByType(FinalistAward) + assert.Nil(t, err) + if assert.Equal(t, 1, len(awards)) { + assert.Equal(t, award2, awards[0]) + } + awards, err = db.GetAwardsByType(WinnerAward) + assert.Nil(t, err) + if assert.Equal(t, 2, len(awards)) { + assert.Equal(t, award1, awards[0]) + assert.Equal(t, award4, awards[1]) + } +} diff --git a/model/database.go b/model/database.go index 16cbdd2..3402383 100644 --- a/model/database.go +++ b/model/database.go @@ -36,6 +36,7 @@ type Database struct { lowerThirdMap *modl.DbMap sponsorSlideMap *modl.DbMap scheduleBlockMap *modl.DbMap + awardMap *modl.DbMap } // Opens the SQLite database at the given path, creating it if it doesn't exist, and runs any pending @@ -124,6 +125,9 @@ func (database *Database) mapTables() { database.scheduleBlockMap = modl.NewDbMap(database.db, dialect) database.scheduleBlockMap.AddTableWithName(ScheduleBlock{}, "schedule_blocks").SetKeys(true, "Id") + + database.awardMap = modl.NewDbMap(database.db, dialect) + database.awardMap.AddTableWithName(Award{}, "awards").SetKeys(true, "Id") } func serializeHelper(target *string, source interface{}) error { diff --git a/model/lower_third.go b/model/lower_third.go index 637c542..0ccbf17 100644 --- a/model/lower_third.go +++ b/model/lower_third.go @@ -10,6 +10,7 @@ type LowerThird struct { TopText string BottomText string DisplayOrder int + AwardId int } func (database *Database) CreateLowerThird(lowerThird *LowerThird) error { @@ -45,3 +46,16 @@ func (database *Database) GetAllLowerThirds() ([]LowerThird, error) { err := database.lowerThirdMap.Select(&lowerThirds, "SELECT * FROM lower_thirds ORDER BY displayorder") return lowerThirds, err } + +func (database *Database) GetLowerThirdsByAwardId(awardId int) ([]LowerThird, error) { + var lowerThirds []LowerThird + err := database.lowerThirdMap.Select(&lowerThirds, "SELECT * FROM lower_thirds WHERE awardid = ? ORDER BY id", + awardId) + return lowerThirds, err +} + +func (database *Database) GetNextLowerThirdDisplayOrder() int { + var count int + _ = database.lowerThirdMap.SelectOne(&count, "SELECT MAX(displayorder) + 1 FROM lower_thirds") + return count +} diff --git a/model/lower_third_test.go b/model/lower_third_test.go index 68e116b..483c5f7 100644 --- a/model/lower_third_test.go +++ b/model/lower_third_test.go @@ -19,7 +19,7 @@ func TestGetNonexistentLowerThird(t *testing.T) { func TestLowerThirdCrud(t *testing.T) { db := setupTestDb(t) - lowerThird := LowerThird{0, "Top Text", "Bottom Text", 0} + lowerThird := LowerThird{0, "Top Text", "Bottom Text", 0, 0} db.CreateLowerThird(&lowerThird) lowerThird2, err := db.GetLowerThirdById(1) assert.Nil(t, err) @@ -40,10 +40,36 @@ func TestLowerThirdCrud(t *testing.T) { func TestTruncateLowerThirds(t *testing.T) { db := setupTestDb(t) - lowerThird := LowerThird{0, "Top Text", "Bottom Text", 0} + lowerThird := LowerThird{0, "Top Text", "Bottom Text", 0, 0} db.CreateLowerThird(&lowerThird) db.TruncateLowerThirds() lowerThird2, err := db.GetLowerThirdById(1) assert.Nil(t, err) assert.Nil(t, lowerThird2) } + +func TestGetLowerThirdsByAwardId(t *testing.T) { + db := setupTestDb(t) + lowerThird1 := LowerThird{0, "Top Text", "Bottom Text", 0, 0} + db.CreateLowerThird(&lowerThird1) + lowerThird2 := LowerThird{0, "Award 1", "", 1, 5} + db.CreateLowerThird(&lowerThird2) + lowerThird3 := LowerThird{0, "Award 2", "", 2, 2} + db.CreateLowerThird(&lowerThird3) + lowerThird4 := LowerThird{0, "Award 1", "Award 1 Winner", 3, 5} + db.CreateLowerThird(&lowerThird4) + nextDisplayOrder := db.GetNextLowerThirdDisplayOrder() + assert.Equal(t, 4, nextDisplayOrder) + + lowerThirds, err := db.GetLowerThirdsByAwardId(5) + assert.Nil(t, err) + if assert.Equal(t, 2, len(lowerThirds)) { + assert.Equal(t, lowerThird2, lowerThirds[0]) + assert.Equal(t, lowerThird4, lowerThirds[1]) + } + lowerThirds, err = db.GetLowerThirdsByAwardId(2) + assert.Nil(t, err) + if assert.Equal(t, 1, len(lowerThirds)) { + assert.Equal(t, lowerThird3, lowerThirds[0]) + } +} diff --git a/templates/base.html b/templates/base.html index 298e6a5..a3e0817 100644 --- a/templates/base.html +++ b/templates/base.html @@ -31,6 +31,7 @@
  • Settings
  • Team List
  • Match Scheduling
  • +
  • Awards
  • Lower Thirds
  • Sponsor Slides
  • Display Configuration
  • diff --git a/templates/setup_awards.html b/templates/setup_awards.html new file mode 100644 index 0000000..f4d98d2 --- /dev/null +++ b/templates/setup_awards.html @@ -0,0 +1,62 @@ +{{/* + Copyright 2019 Team 254. All Rights Reserved. + Author: pat@patfairbank.com (Patrick Fairbank) + + UI for configuring the awards. +*/}} +{{define "title"}}Awards Configuration{{end}} +{{define "body"}} +
    +
    +
    + Awards Configuration + {{range $award := .Awards}} +
    +
    +
    + +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    + + {{if gt $award.Id 0}} + + {{end}} +
    +
    +
    + {{end}} + Winner and Finalist awards will be automatically generated once the playoff tournament is complete. +
    +
    +
    +{{end}} +{{define "script"}} +{{end}} diff --git a/tournament/awards.go b/tournament/awards.go new file mode 100644 index 0000000..bb02785 --- /dev/null +++ b/tournament/awards.go @@ -0,0 +1,168 @@ +// Copyright 2019 Team 254. All Rights Reserved. +// Author: pat@patfairbank.com (Patrick Fairbank) +// +// Functions for managing awards and their associated lower thirds. + +package tournament + +import ( + "fmt" + "github.com/Team254/cheesy-arena/model" +) + +// Creates or updates the given award, depending on whether or not it already exists. +func CreateOrUpdateAward(database *model.Database, award *model.Award, createIntroLowerThird bool) error { + // Validate the award data. + if award.AwardName == "" { + return fmt.Errorf("Award name cannot be blank.") + } + var team *model.Team + if award.TeamId > 0 { + team, _ = database.GetTeamById(award.TeamId) + if team == nil { + return fmt.Errorf("Team %d is not present at this event.", award.TeamId) + } + } + + var err error + if award.Id == 0 { + err = database.CreateAward(award) + } else { + err = database.SaveAward(award) + } + if err != nil { + return err + } + + // Create or update associated lower thirds. + awardIntroLowerThird := model.LowerThird{TopText: award.AwardName, AwardId: award.Id} + awardWinnerLowerThird := model.LowerThird{TopText: award.AwardName, BottomText: award.PersonName, + AwardId: award.Id} + if team != nil { + if award.PersonName == "" { + awardWinnerLowerThird.BottomText = fmt.Sprintf("Team %d, %s", team.Id, team.Nickname) + } else { + awardWinnerLowerThird.BottomText = fmt.Sprintf("%s – Team %d, %s", award.PersonName, team.Id, + team.Nickname) + } + } + if awardWinnerLowerThird.BottomText == "" { + awardWinnerLowerThird.BottomText = "(No awardee assigned yet)" + } + lowerThirds, err := database.GetLowerThirdsByAwardId(award.Id) + if err != nil { + return err + } + bottomIndex := 0 + if createIntroLowerThird { + if err = createOrUpdateAwardLowerThird(database, &awardIntroLowerThird, lowerThirds, 0); err != nil { + return err + } + bottomIndex++ + } + if err = createOrUpdateAwardLowerThird(database, &awardWinnerLowerThird, lowerThirds, bottomIndex); err != nil { + return err + } + + return nil +} + +// Deletes the given award and any associated lower thirds. +func DeleteAward(database *model.Database, awardId int) error { + var award *model.Award + award, err := database.GetAwardById(awardId) + if err != nil { + return err + } + if award == nil { + return fmt.Errorf("Award with ID %d does not exist.", awardId) + } + if err = database.DeleteAward(award); err != nil { + return err + } + + // Delete lower thirds. + lowerThirds, err := database.GetLowerThirdsByAwardId(award.Id) + if err != nil { + return err + } + for _, lowerThird := range lowerThirds { + if err = database.DeleteLowerThird(&lowerThird); err != nil { + return err + } + } + + return nil +} + +// Generates awards and lower thirds for the tournament winners and finalists. +func CreateOrUpdateWinnerAndFinalistAwards(database *model.Database, winnerAllianceId, finalistAllianceId int) error { + var winnerAllianceTeams, finalistAllianceTeams []model.AllianceTeam + var err error + if winnerAllianceTeams, err = database.GetTeamsByAlliance(winnerAllianceId); err != nil { + return err + } + if finalistAllianceTeams, err = database.GetTeamsByAlliance(finalistAllianceId); err != nil { + return err + } + if len(winnerAllianceTeams) == 0 || len(finalistAllianceTeams) == 0 { + return fmt.Errorf("Input alliances do not contain any teams.") + } + + // Clear out any awards that may exist if the final match was scored more than once. + winnerAwards, err := database.GetAwardsByType(model.WinnerAward) + if err != nil { + return err + } + finalistAwards, err := database.GetAwardsByType(model.FinalistAward) + if err != nil { + return err + } + for _, award := range append(winnerAwards, finalistAwards...) { + if err = DeleteAward(database, award.Id); err != nil { + return err + } + } + + // Create the finalist awards first since they're usually presented first. + finalistAward := model.Award{AwardName: "Event Finalist", Type: model.FinalistAward, + TeamId: finalistAllianceTeams[0].TeamId} + if err = CreateOrUpdateAward(database, &finalistAward, true); err != nil { + return err + } + for _, allianceTeam := range finalistAllianceTeams[1:] { + finalistAward.Id = 0 + finalistAward.TeamId = allianceTeam.TeamId + if err = CreateOrUpdateAward(database, &finalistAward, false); err != nil { + return err + } + } + + // Create the winner awards. + winnerAward := model.Award{AwardName: "Event Winner", Type: model.WinnerAward, + TeamId: winnerAllianceTeams[0].TeamId} + if err = CreateOrUpdateAward(database, &winnerAward, true); err != nil { + return err + } + for _, allianceTeam := range winnerAllianceTeams[1:] { + winnerAward.Id = 0 + winnerAward.TeamId = allianceTeam.TeamId + if err = CreateOrUpdateAward(database, &winnerAward, false); err != nil { + return err + } + } + + return nil +} + +func createOrUpdateAwardLowerThird(database *model.Database, lowerThird *model.LowerThird, + existingLowerThirds []model.LowerThird, index int) error { + if index < len(existingLowerThirds) { + lowerThird.Id = existingLowerThirds[index].Id + lowerThird.DisplayOrder = existingLowerThirds[index].DisplayOrder + return database.SaveLowerThird(lowerThird) + } else { + lowerThird.DisplayOrder = database.GetNextLowerThirdDisplayOrder() + return database.CreateLowerThird(lowerThird) + } +} diff --git a/tournament/awards_test.go b/tournament/awards_test.go new file mode 100644 index 0000000..2873b59 --- /dev/null +++ b/tournament/awards_test.go @@ -0,0 +1,146 @@ +// Copyright 2019 Team 254. All Rights Reserved. +// Author: pat@patfairbank.com (Patrick Fairbank) + +package tournament + +import ( + "github.com/Team254/cheesy-arena/model" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestCreateOrUpdateAwardWithIntro(t *testing.T) { + database := setupTestDb(t) + database.CreateTeam(&model.Team{Id: 254, Nickname: "Teh Chezy Pofs"}) + + award := model.Award{0, model.JudgedAward, "Safety Award", 0, ""} + err := CreateOrUpdateAward(database, &award, true) + assert.Nil(t, err) + award2, _ := database.GetAwardById(award.Id) + assert.Equal(t, award, *award2) + lowerThirds, _ := database.GetAllLowerThirds() + if assert.Equal(t, 2, len(lowerThirds)) { + assert.Equal(t, "Safety Award", lowerThirds[0].TopText) + assert.Equal(t, "", lowerThirds[0].BottomText) + assert.Equal(t, "Safety Award", lowerThirds[1].TopText) + assert.Equal(t, "(No awardee assigned yet)", lowerThirds[1].BottomText) + } + + award.AwardName = "Saftey Award" + award.TeamId = 254 + err = CreateOrUpdateAward(database, &award, true) + assert.Nil(t, err) + award2, _ = database.GetAwardById(award.Id) + assert.Equal(t, award, *award2) + lowerThirds, _ = database.GetAllLowerThirds() + if assert.Equal(t, 2, len(lowerThirds)) { + assert.Equal(t, "Saftey Award", lowerThirds[0].TopText) + assert.Equal(t, "", lowerThirds[0].BottomText) + assert.Equal(t, "Saftey Award", lowerThirds[1].TopText) + assert.Equal(t, "Team 254, Teh Chezy Pofs", lowerThirds[1].BottomText) + } + + err = DeleteAward(database, award.Id) + assert.Nil(t, err) + award2, _ = database.GetAwardById(award.Id) + assert.Nil(t, award2) + lowerThirds, _ = database.GetAllLowerThirds() + assert.Empty(t, lowerThirds) +} + +func TestCreateOrUpdateAwardWithoutIntro(t *testing.T) { + database := setupTestDb(t) + database.CreateTeam(&model.Team{Id: 254, Nickname: "Teh Chezy Pofs"}) + otherLowerThird := model.LowerThird{TopText: "Marco", BottomText: "Polo"} + database.CreateLowerThird(&otherLowerThird) + + award := model.Award{0, model.WinnerAward, "Event Winner", 0, "Bob Dorough"} + err := CreateOrUpdateAward(database, &award, false) + assert.Nil(t, err) + award2, _ := database.GetAwardById(award.Id) + assert.Equal(t, award, *award2) + lowerThirds, _ := database.GetAllLowerThirds() + if assert.Equal(t, 2, len(lowerThirds)) { + assert.Equal(t, otherLowerThird, lowerThirds[0]) + assert.Equal(t, "Event Winner", lowerThirds[1].TopText) + assert.Equal(t, "Bob Dorough", lowerThirds[1].BottomText) + } + + award.TeamId = 254 + err = CreateOrUpdateAward(database, &award, false) + assert.Nil(t, err) + award2, _ = database.GetAwardById(award.Id) + assert.Equal(t, award, *award2) + lowerThirds, _ = database.GetAllLowerThirds() + if assert.Equal(t, 2, len(lowerThirds)) { + assert.Equal(t, otherLowerThird, lowerThirds[0]) + assert.Equal(t, "Event Winner", lowerThirds[1].TopText) + assert.Equal(t, "Bob Dorough – Team 254, Teh Chezy Pofs", lowerThirds[1].BottomText) + } + + err = DeleteAward(database, award.Id) + assert.Nil(t, err) + award2, _ = database.GetAwardById(award.Id) + assert.Nil(t, award2) + lowerThirds, _ = database.GetAllLowerThirds() + if assert.Equal(t, 1, len(lowerThirds)) { + assert.Equal(t, otherLowerThird, lowerThirds[0]) + } +} + +func TestCreateOrUpdateWinnerAndFinalistAwards(t *testing.T) { + database := setupTestDb(t) + CreateTestAlliances(database, 2) + database.CreateTeam(&model.Team{Id: 1}) + database.CreateTeam(&model.Team{Id: 10}) + database.CreateTeam(&model.Team{Id: 100}) + database.CreateTeam(&model.Team{Id: 2}) + database.CreateTeam(&model.Team{Id: 20}) + database.CreateTeam(&model.Team{Id: 200}) + + err := CreateOrUpdateWinnerAndFinalistAwards(database, 2, 1) + assert.Nil(t, err) + awards, _ := database.GetAllAwards() + if assert.Equal(t, 6, len(awards)) { + assert.Equal(t, model.Award{1, model.FinalistAward, "Event Finalist", 1, ""}, awards[0]) + assert.Equal(t, model.Award{2, model.FinalistAward, "Event Finalist", 10, ""}, awards[1]) + assert.Equal(t, model.Award{3, model.FinalistAward, "Event Finalist", 100, ""}, awards[2]) + assert.Equal(t, model.Award{4, model.WinnerAward, "Event Winner", 2, ""}, awards[3]) + assert.Equal(t, model.Award{5, model.WinnerAward, "Event Winner", 20, ""}, awards[4]) + assert.Equal(t, model.Award{6, model.WinnerAward, "Event Winner", 200, ""}, awards[5]) + } + lowerThirds, _ := database.GetAllLowerThirds() + if assert.Equal(t, 8, len(lowerThirds)) { + assert.Equal(t, "Event Finalist", lowerThirds[0].TopText) + assert.Equal(t, "", lowerThirds[0].BottomText) + assert.Equal(t, "Event Finalist", lowerThirds[1].TopText) + assert.Equal(t, "Team 1, ", lowerThirds[1].BottomText) + assert.Equal(t, "Event Winner", lowerThirds[4].TopText) + assert.Equal(t, "", lowerThirds[4].BottomText) + assert.Equal(t, "Event Winner", lowerThirds[5].TopText) + assert.Equal(t, "Team 2, ", lowerThirds[5].BottomText) + } + + err = CreateOrUpdateWinnerAndFinalistAwards(database, 1, 2) + assert.Nil(t, err) + awards, _ = database.GetAllAwards() + if assert.Equal(t, 6, len(awards)) { + assert.Equal(t, model.Award{1, model.FinalistAward, "Event Finalist", 2, ""}, awards[0]) + assert.Equal(t, model.Award{2, model.FinalistAward, "Event Finalist", 20, ""}, awards[1]) + assert.Equal(t, model.Award{3, model.FinalistAward, "Event Finalist", 200, ""}, awards[2]) + assert.Equal(t, model.Award{4, model.WinnerAward, "Event Winner", 1, ""}, awards[3]) + assert.Equal(t, model.Award{5, model.WinnerAward, "Event Winner", 10, ""}, awards[4]) + assert.Equal(t, model.Award{6, model.WinnerAward, "Event Winner", 100, ""}, awards[5]) + } + lowerThirds, _ = database.GetAllLowerThirds() + if assert.Equal(t, 8, len(lowerThirds)) { + assert.Equal(t, "Event Finalist", lowerThirds[0].TopText) + assert.Equal(t, "", lowerThirds[0].BottomText) + assert.Equal(t, "Event Finalist", lowerThirds[1].TopText) + assert.Equal(t, "Team 2, ", lowerThirds[1].BottomText) + assert.Equal(t, "Event Winner", lowerThirds[4].TopText) + assert.Equal(t, "", lowerThirds[4].BottomText) + assert.Equal(t, "Event Winner", lowerThirds[5].TopText) + assert.Equal(t, "Team 1, ", lowerThirds[5].BottomText) + } +} diff --git a/web/match_play.go b/web/match_play.go index 0414752..d5c7ed6 100644 --- a/web/match_play.go +++ b/web/match_play.go @@ -377,11 +377,27 @@ func (web *Web) commitMatchScore(match *model.Match, matchResult *model.MatchRes if match.ShouldUpdateEliminationMatches() { // Generate any subsequent elimination matches. - _, err = tournament.UpdateEliminationSchedule(web.arena.Database, + isTournamentWon, err := tournament.UpdateEliminationSchedule(web.arena.Database, time.Now().Add(time.Second*tournament.ElimMatchSpacingSec)) if err != nil { return err } + + // Generate awards if the tournament is over. + if isTournamentWon { + var winnerAllianceId, finalistAllianceId int + if match.Winner == "R" { + winnerAllianceId = match.ElimRedAlliance + finalistAllianceId = match.ElimBlueAlliance + } else if match.Winner == "B" { + winnerAllianceId = match.ElimBlueAlliance + finalistAllianceId = match.ElimRedAlliance + } + if err = tournament.CreateOrUpdateWinnerAndFinalistAwards(web.arena.Database, winnerAllianceId, + finalistAllianceId); err != nil { + return err + } + } } if web.arena.EventSettings.TbaPublishingEnabled && match.Type != "practice" { diff --git a/web/setup_awards.go b/web/setup_awards.go new file mode 100644 index 0000000..5205843 --- /dev/null +++ b/web/setup_awards.go @@ -0,0 +1,75 @@ +// Copyright 2019 Team 254. All Rights Reserved. +// Author: pat@patfairbank.com (Patrick Fairbank) +// +// Web routes for managing awards. + +package web + +import ( + "github.com/Team254/cheesy-arena/model" + "github.com/Team254/cheesy-arena/tournament" + "net/http" + "strconv" +) + +// Shows the awards configuration page. +func (web *Web) awardsGetHandler(w http.ResponseWriter, r *http.Request) { + if !web.userIsAdmin(w, r) { + return + } + + template, err := web.parseFiles("templates/setup_awards.html", "templates/base.html") + if err != nil { + handleWebErr(w, err) + return + } + awards, err := web.arena.Database.GetAllAwards() + if err != nil { + handleWebErr(w, err) + return + } + teams, err := web.arena.Database.GetAllTeams() + if err != nil { + handleWebErr(w, err) + return + } + + // Append a blank award to the end that can be used to add a new one. + awards = append(awards, model.Award{}) + + data := struct { + *model.EventSettings + Awards []model.Award + Teams []model.Team + }{web.arena.EventSettings, awards, teams} + err = template.ExecuteTemplate(w, "base", data) + if err != nil { + handleWebErr(w, err) + return + } +} + +// Saves the new or modified awards to the database. +func (web *Web) awardsPostHandler(w http.ResponseWriter, r *http.Request) { + if !web.userIsAdmin(w, r) { + return + } + + awardId, _ := strconv.Atoi(r.PostFormValue("id")) + if r.PostFormValue("action") == "delete" { + if err := tournament.DeleteAward(web.arena.Database, awardId); err != nil { + handleWebErr(w, err) + return + } + } else { + teamId, _ := strconv.Atoi(r.PostFormValue("teamId")) + award := model.Award{Id: awardId, Type: model.JudgedAward, AwardName: r.PostFormValue("awardName"), + TeamId: teamId, PersonName: r.PostFormValue("personName")} + if err := tournament.CreateOrUpdateAward(web.arena.Database, &award, true); err != nil { + handleWebErr(w, err) + return + } + } + + http.Redirect(w, r, "/setup/awards", 303) +} diff --git a/web/setup_awards_test.go b/web/setup_awards_test.go new file mode 100644 index 0000000..269be7b --- /dev/null +++ b/web/setup_awards_test.go @@ -0,0 +1,35 @@ +// Copyright 2019 Team 254. All Rights Reserved. +// Author: pat@patfairbank.com (Patrick Fairbank) + +package web + +import ( + "github.com/Team254/cheesy-arena/model" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestSetupAwards(t *testing.T) { + web := setupTestWeb(t) + + web.arena.Database.CreateAward(&model.Award{0, model.JudgedAward, "Spirit Award", 0, ""}) + web.arena.Database.CreateAward(&model.Award{0, model.JudgedAward, "Saftey Award", 0, ""}) + + recorder := web.getHttpResponse("/setup/awards") + assert.Equal(t, 200, recorder.Code) + assert.Contains(t, recorder.Body.String(), "Spirit Award") + assert.Contains(t, recorder.Body.String(), "Saftey Award") + + recorder = web.postHttpResponse("/setup/awards", "action=delete&id=1") + assert.Equal(t, 303, recorder.Code) + recorder = web.getHttpResponse("/setup/awards") + assert.Equal(t, 200, recorder.Code) + assert.NotContains(t, recorder.Body.String(), "Spirit Award") + assert.Contains(t, recorder.Body.String(), "Saftey Award") + + recorder = web.postHttpResponse("/setup/awards", "awardId=2&awardName=Saftey+Award&personName=Englebert") + assert.Equal(t, 303, recorder.Code) + recorder = web.getHttpResponse("/setup/awards") + assert.Equal(t, 200, recorder.Code) + assert.Contains(t, recorder.Body.String(), "Englebert") +} diff --git a/web/setup_lower_thirds.go b/web/setup_lower_thirds.go index eb348ad..f9c5525 100644 --- a/web/setup_lower_thirds.go +++ b/web/setup_lower_thirds.go @@ -149,6 +149,7 @@ func (web *Web) saveLowerThird(lowerThird *model.LowerThird) error { // Create or update lower third. if oldLowerThird == nil { + lowerThird.DisplayOrder = web.arena.Database.GetNextLowerThirdDisplayOrder() err = web.arena.Database.CreateLowerThird(lowerThird) } else { err = web.arena.Database.SaveLowerThird(lowerThird) diff --git a/web/setup_lower_thirds_test.go b/web/setup_lower_thirds_test.go index 8ade57b..d5dcc91 100644 --- a/web/setup_lower_thirds_test.go +++ b/web/setup_lower_thirds_test.go @@ -15,9 +15,9 @@ import ( func TestSetupLowerThirds(t *testing.T) { web := setupTestWeb(t) - web.arena.Database.CreateLowerThird(&model.LowerThird{0, "Top Text 1", "Bottom Text 1", 0}) - web.arena.Database.CreateLowerThird(&model.LowerThird{0, "Top Text 2", "Bottom Text 2", 1}) - web.arena.Database.CreateLowerThird(&model.LowerThird{0, "Top Text 3", "Bottom Text 3", 2}) + web.arena.Database.CreateLowerThird(&model.LowerThird{0, "Top Text 1", "Bottom Text 1", 0, 0}) + web.arena.Database.CreateLowerThird(&model.LowerThird{0, "Top Text 2", "Bottom Text 2", 1, 0}) + web.arena.Database.CreateLowerThird(&model.LowerThird{0, "Top Text 3", "Bottom Text 3", 2, 0}) recorder := web.getHttpResponse("/setup/lower_thirds") assert.Equal(t, 200, recorder.Code) @@ -31,24 +31,24 @@ func TestSetupLowerThirds(t *testing.T) { defer conn.Close() ws := websocket.NewTestWebsocket(conn) - ws.Write("saveLowerThird", model.LowerThird{1, "Top Text 4", "Bottom Text 1", 0}) + ws.Write("saveLowerThird", model.LowerThird{1, "Top Text 4", "Bottom Text 1", 0, 0}) time.Sleep(time.Millisecond * 10) // Allow some time for the command to be processed. lowerThird, _ := web.arena.Database.GetLowerThirdById(1) assert.Equal(t, "Top Text 4", lowerThird.TopText) - ws.Write("deleteLowerThird", model.LowerThird{1, "Top Text 4", "Bottom Text 1", 0}) + ws.Write("deleteLowerThird", model.LowerThird{1, "Top Text 4", "Bottom Text 1", 0, 0}) time.Sleep(time.Millisecond * 10) lowerThird, _ = web.arena.Database.GetLowerThirdById(1) assert.Nil(t, lowerThird) assert.Equal(t, "blank", web.arena.AudienceDisplayMode) - ws.Write("showLowerThird", model.LowerThird{2, "Top Text 5", "Bottom Text 1", 0}) + ws.Write("showLowerThird", model.LowerThird{2, "Top Text 5", "Bottom Text 1", 0, 0}) time.Sleep(time.Millisecond * 10) lowerThird, _ = web.arena.Database.GetLowerThirdById(2) assert.Equal(t, "Top Text 5", lowerThird.TopText) assert.Equal(t, "lowerThird", web.arena.AudienceDisplayMode) - ws.Write("hideLowerThird", model.LowerThird{2, "Top Text 6", "Bottom Text 1", 0}) + ws.Write("hideLowerThird", model.LowerThird{2, "Top Text 6", "Bottom Text 1", 0, 0}) time.Sleep(time.Millisecond * 10) lowerThird, _ = web.arena.Database.GetLowerThirdById(2) assert.Equal(t, "Top Text 6", lowerThird.TopText) diff --git a/web/web.go b/web/web.go index 28308f2..a03e2ab 100644 --- a/web/web.go +++ b/web/web.go @@ -183,6 +183,8 @@ func (web *Web) newHandler() http.Handler { router.HandleFunc("/reports/csv/teams", web.teamsCsvReportHandler).Methods("GET") router.HandleFunc("/reports/pdf/teams", web.teamsPdfReportHandler).Methods("GET") router.HandleFunc("/reports/csv/wpa_keys", web.wpaKeysCsvReportHandler).Methods("GET") + router.HandleFunc("/setup/awards", web.awardsGetHandler).Methods("GET") + router.HandleFunc("/setup/awards", web.awardsPostHandler).Methods("POST") router.HandleFunc("/setup/db/clear", web.clearDbHandler).Methods("POST") router.HandleFunc("/setup/db/restore", web.restoreDbHandler).Methods("POST") router.HandleFunc("/setup/db/save", web.saveDbHandler).Methods("GET")