Convert EventSettings, Match, and MatchResult models to use Bolt DB.

This commit is contained in:
Patrick Fairbank
2021-05-12 16:40:07 -07:00
parent ea4d56e665
commit 1d523c5f37
28 changed files with 274 additions and 375 deletions

View File

@@ -1,37 +0,0 @@
-- +goose Up
CREATE TABLE event_settings (
id INTEGER PRIMARY KEY,
name VARCHAR(255),
numelimalliances int,
selectionround2order VARCHAR(1),
selectionround3order VARCHAR(1),
teaminfodownloadenabled bool,
tbapublishingenabled bool,
tbaeventcode VARCHAR(16),
tbasecretid VARCHAR(255),
tbasecret VARCHAR(255),
networksecurityenabled bool,
apaddress VARCHAR(255),
apusername VARCHAR(255),
appassword VARCHAR(255),
apteamchannel int,
apadminchannel int,
apadminwpakey VARCHAR(255),
ap2address VARCHAR(255),
ap2username VARCHAR(255),
ap2password VARCHAR(255),
ap2teamchannel int,
switchaddress VARCHAR(255),
switchpassword VARCHAR(255),
plcaddress VARCHAR(255),
tbadownloadenabled bool,
adminpassword VARCHAR(255),
warmupdurationsec int,
autodurationsec int,
pausedurationsec int,
teleopdurationsec int,
warningremainingdurationsec int
);
-- +goose Down
DROP TABLE event_settings;

View File

@@ -1,31 +0,0 @@
-- +goose Up
CREATE TABLE matches (
id INTEGER PRIMARY KEY,
type VARCHAR(16),
displayname VARCHAR(16),
time DATETIME,
elimround int,
elimgroup int,
eliminstance int,
elimredalliance int,
elimbluealliance int,
red1 int,
red1issurrogate bool,
red2 int,
red2issurrogate bool,
red3 int,
red3issurrogate bool,
blue1 int,
blue1issurrogate bool,
blue2 int,
blue2issurrogate bool,
blue3 int,
blue3issurrogate bool,
startedat DATETIME,
scorecommittedat DATETIME,
status VARCHAR(16)
);
CREATE UNIQUE INDEX type_displayname ON matches(type, displayname);
-- +goose Down
DROP TABLE matches;

View File

@@ -1,13 +0,0 @@
-- +goose Up
CREATE TABLE match_results (
id INTEGER PRIMARY KEY,
matchid int,
playnumber int,
matchtype VARCHAR(16),
redscorejson text,
bluescorejson text
);
CREATE UNIQUE INDEX matchid_playnumber ON match_results(matchid, playnumber);
-- +goose Down
DROP TABLE match_results;

View File

@@ -263,7 +263,7 @@ func (arena *Arena) SubstituteTeam(teamId int, station string) error {
arena.MatchLoadNotifier.Notify()
if arena.CurrentMatch.Type != "test" {
arena.Database.SaveMatch(arena.CurrentMatch)
arena.Database.UpdateMatch(arena.CurrentMatch)
}
return nil
}
@@ -275,7 +275,7 @@ func (arena *Arena) StartMatch() error {
// Save the match start time and game-specifc data to the database for posterity.
arena.CurrentMatch.StartedAt = time.Now()
if arena.CurrentMatch.Type != "test" {
arena.Database.SaveMatch(arena.CurrentMatch)
arena.Database.UpdateMatch(arena.CurrentMatch)
}
arena.updateCycleTime(arena.CurrentMatch.StartedAt)

View File

@@ -82,7 +82,7 @@ func (arena *Arena) generateArenaStatusMessage() interface{} {
}
return &struct {
MatchId int
MatchId int64
AllianceStations map[string]*AllianceStation
TeamWifiStatuses map[string]network.TeamWifiStatus
MatchState

View File

@@ -388,13 +388,13 @@ func TestLoadNextMatch(t *testing.T) {
arena.Database.CreateMatch(&qualificationMatch2)
// Test match should be followed by another, empty test match.
assert.Equal(t, 0, arena.CurrentMatch.Id)
assert.Equal(t, int64(0), arena.CurrentMatch.Id)
err := arena.SubstituteTeam(1114, "R1")
assert.Nil(t, err)
arena.CurrentMatch.Status = model.TieMatch
err = arena.LoadNextMatch()
assert.Nil(t, err)
assert.Equal(t, 0, arena.CurrentMatch.Id)
assert.Equal(t, int64(0), arena.CurrentMatch.Id)
assert.Equal(t, 0, arena.CurrentMatch.Red1)
assert.Equal(t, false, arena.CurrentMatch.IsComplete())
@@ -405,15 +405,15 @@ func TestLoadNextMatch(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, practiceMatch1.Id, arena.CurrentMatch.Id)
practiceMatch1.Status = model.RedWonMatch
arena.Database.SaveMatch(&practiceMatch1)
arena.Database.UpdateMatch(&practiceMatch1)
err = arena.LoadNextMatch()
assert.Nil(t, err)
assert.Equal(t, practiceMatch3.Id, arena.CurrentMatch.Id)
practiceMatch3.Status = model.BlueWonMatch
arena.Database.SaveMatch(&practiceMatch3)
arena.Database.UpdateMatch(&practiceMatch3)
err = arena.LoadNextMatch()
assert.Nil(t, err)
assert.Equal(t, 0, arena.CurrentMatch.Id)
assert.Equal(t, int64(0), arena.CurrentMatch.Id)
assert.Equal(t, "test", arena.CurrentMatch.Type)
err = arena.LoadMatch(&qualificationMatch1)

View File

@@ -122,5 +122,5 @@ func setMatch(database *model.Database, match *model.Match, matchTime time.Time,
} else {
match.Status = model.MatchNotPlayed
}
_ = database.SaveMatch(match)
_ = database.UpdateMatch(match)
}

View File

@@ -15,12 +15,12 @@ type AllianceTeam struct {
}
func (database *Database) CreateAllianceTeam(allianceTeam *AllianceTeam) error {
return database.tables[AllianceTeam{}].create(allianceTeam)
return database.allianceTeamTable.create(allianceTeam)
}
func (database *Database) GetTeamsByAlliance(allianceId int) ([]AllianceTeam, error) {
var allianceTeams []AllianceTeam
if err := database.tables[AllianceTeam{}].getAll(&allianceTeams); err != nil {
if err := database.allianceTeamTable.getAll(&allianceTeams); err != nil {
return nil, err
}
sort.Slice(allianceTeams, func(i, j int) bool {
@@ -37,20 +37,20 @@ func (database *Database) GetTeamsByAlliance(allianceId int) ([]AllianceTeam, er
}
func (database *Database) UpdateAllianceTeam(allianceTeam *AllianceTeam) error {
return database.tables[AllianceTeam{}].update(allianceTeam)
return database.allianceTeamTable.update(allianceTeam)
}
func (database *Database) DeleteAllianceTeam(id int64) error {
return database.tables[AllianceTeam{}].delete(id)
return database.allianceTeamTable.delete(id)
}
func (database *Database) TruncateAllianceTeams() error {
return database.tables[AllianceTeam{}].truncate()
return database.allianceTeamTable.truncate()
}
func (database *Database) GetAllAlliances() ([][]AllianceTeam, error) {
var allianceTeams []AllianceTeam
if err := database.tables[AllianceTeam{}].getAll(&allianceTeams); err != nil {
if err := database.allianceTeamTable.getAll(&allianceTeams); err != nil {
return nil, err
}
sort.Slice(allianceTeams, func(i, j int) bool {

View File

@@ -24,30 +24,30 @@ const (
)
func (database *Database) CreateAward(award *Award) error {
return database.tables[Award{}].create(award)
return database.awardTable.create(award)
}
func (database *Database) GetAwardById(id int64) (*Award, error) {
var award *Award
err := database.tables[Award{}].getById(id, &award)
err := database.awardTable.getById(id, &award)
return award, err
}
func (database *Database) UpdateAward(award *Award) error {
return database.tables[Award{}].update(award)
return database.awardTable.update(award)
}
func (database *Database) DeleteAward(id int64) error {
return database.tables[Award{}].delete(id)
return database.awardTable.delete(id)
}
func (database *Database) TruncateAwards() error {
return database.tables[Award{}].truncate()
return database.awardTable.truncate()
}
func (database *Database) GetAllAwards() ([]Award, error) {
var awards []Award
if err := database.tables[Award{}].getAll(&awards); err != nil {
if err := database.awardTable.getAll(&awards); err != nil {
return nil, err
}
sort.Slice(awards, func(i, j int) bool {

View File

@@ -24,25 +24,22 @@ const backupsDir = "db/backups"
const migrationsDir = "db/migrations"
var BaseDir = "." // Mutable for testing
var recordTypes = []interface{}{
AllianceTeam{},
Award{},
LowerThird{},
}
type Database struct {
Path string
db *sql.DB
eventSettingsMap *modl.DbMap
matchMap *modl.DbMap
matchResultMap *modl.DbMap
rankingMap *modl.DbMap
teamMap *modl.DbMap
sponsorSlideMap *modl.DbMap
scheduleBlockMap *modl.DbMap
userSessionMap *modl.DbMap
bolt *bbolt.DB
tables map[interface{}]*table
Path string
db *sql.DB
rankingMap *modl.DbMap
teamMap *modl.DbMap
sponsorSlideMap *modl.DbMap
scheduleBlockMap *modl.DbMap
userSessionMap *modl.DbMap
bolt *bbolt.DB
allianceTeamTable *table
awardTable *table
eventSettingsTable *table
lowerThirdTable *table
matchTable *table
matchResultTable *table
}
// Opens the SQLite database at the given path, creating it if it doesn't exist, and runs any pending
@@ -75,13 +72,23 @@ func OpenDatabase(filename string) (*Database, error) {
}
// Register tables.
database.tables = make(map[interface{}]*table)
for _, recordType := range recordTypes {
table, err := database.newTable(recordType)
if err != nil {
return nil, err
}
database.tables[recordType] = table
if database.allianceTeamTable, err = database.newTable(AllianceTeam{}); err != nil {
return nil, err
}
if database.awardTable, err = database.newTable(Award{}); err != nil {
return nil, err
}
if database.eventSettingsTable, err = database.newTable(EventSettings{}); err != nil {
return nil, err
}
if database.lowerThirdTable, err = database.newTable(LowerThird{}); err != nil {
return nil, err
}
if database.matchTable, err = database.newTable(Match{}); err != nil {
return nil, err
}
if database.matchResultTable, err = database.newTable(MatchResult{}); err != nil {
return nil, err
}
return &database, nil
@@ -121,15 +128,6 @@ func (database *Database) Backup(eventName, reason string) error {
func (database *Database) mapTables() {
dialect := new(modl.SqliteDialect)
database.eventSettingsMap = modl.NewDbMap(database.db, dialect)
database.eventSettingsMap.AddTableWithName(EventSettings{}, "event_settings").SetKeys(false, "Id")
database.matchMap = modl.NewDbMap(database.db, dialect)
database.matchMap.AddTableWithName(Match{}, "matches").SetKeys(true, "Id")
database.matchResultMap = modl.NewDbMap(database.db, dialect)
database.matchResultMap.AddTableWithName(MatchResultDb{}, "match_results").SetKeys(true, "Id")
database.rankingMap = modl.NewDbMap(database.db, dialect)
database.rankingMap.AddTableWithName(RankingDb{}, "rankings").SetKeys(false, "TeamId")

View File

@@ -8,7 +8,7 @@ package model
import "github.com/Team254/cheesy-arena-lite/game"
type EventSettings struct {
Id int
Id int64 `db:"id"`
Name string
NumElimAlliances int
SelectionRound2Order string
@@ -40,38 +40,39 @@ type EventSettings struct {
WarningRemainingDurationSec int
}
const eventSettingsId = 0
func (database *Database) GetEventSettings() (*EventSettings, error) {
eventSettings := new(EventSettings)
err := database.eventSettingsMap.Get(eventSettings, eventSettingsId)
if err != nil {
// Database record doesn't exist yet; create it now.
eventSettings.Name = "Untitled Event"
eventSettings.NumElimAlliances = 8
eventSettings.SelectionRound2Order = "L"
eventSettings.SelectionRound3Order = ""
eventSettings.TBADownloadEnabled = true
eventSettings.ApTeamChannel = 157
eventSettings.ApAdminChannel = 0
eventSettings.ApAdminWpaKey = "1234Five"
eventSettings.Ap2TeamChannel = 0
eventSettings.WarmupDurationSec = game.MatchTiming.WarmupDurationSec
eventSettings.AutoDurationSec = game.MatchTiming.AutoDurationSec
eventSettings.PauseDurationSec = game.MatchTiming.PauseDurationSec
eventSettings.TeleopDurationSec = game.MatchTiming.TeleopDurationSec
eventSettings.WarningRemainingDurationSec = game.MatchTiming.WarningRemainingDurationSec
err = database.eventSettingsMap.Insert(eventSettings)
if err != nil {
return nil, err
}
var allEventSettings []EventSettings
if err := database.eventSettingsTable.getAll(&allEventSettings); err != nil {
return nil, err
}
return eventSettings, nil
if len(allEventSettings) == 1 {
return &allEventSettings[0], nil
}
// Database record doesn't exist yet; create it now.
eventSettings := EventSettings{
Name: "Untitled Event",
NumElimAlliances: 8,
SelectionRound2Order: "L",
SelectionRound3Order: "",
TBADownloadEnabled: true,
ApTeamChannel: 157,
ApAdminChannel: 0,
ApAdminWpaKey: "1234Five",
Ap2TeamChannel: 0,
WarmupDurationSec: game.MatchTiming.WarmupDurationSec,
AutoDurationSec: game.MatchTiming.AutoDurationSec,
PauseDurationSec: game.MatchTiming.PauseDurationSec,
TeleopDurationSec: game.MatchTiming.TeleopDurationSec,
WarningRemainingDurationSec: game.MatchTiming.WarningRemainingDurationSec,
}
if err := database.eventSettingsTable.create(&eventSettings); err != nil {
return nil, err
}
return &eventSettings, nil
}
func (database *Database) SaveEventSettings(eventSettings *EventSettings) error {
eventSettings.Id = eventSettingsId
_, err := database.eventSettingsMap.Update(eventSettings)
return err
func (database *Database) UpdateEventSettings(eventSettings *EventSettings) error {
return database.eventSettingsTable.update(eventSettings)
}

View File

@@ -14,7 +14,7 @@ func TestEventSettingsReadWrite(t *testing.T) {
eventSettings, err := db.GetEventSettings()
assert.Nil(t, err)
assert.Equal(t, EventSettings{Id: 0, Name: "Untitled Event", NumElimAlliances: 8, SelectionRound2Order: "L",
assert.Equal(t, EventSettings{Id: 1, Name: "Untitled Event", NumElimAlliances: 8, SelectionRound2Order: "L",
SelectionRound3Order: "", TBADownloadEnabled: true, ApTeamChannel: 157, ApAdminChannel: 0,
ApAdminWpaKey: "1234Five", WarmupDurationSec: 0, AutoDurationSec: 15, PauseDurationSec: 2,
TeleopDurationSec: 135, WarningRemainingDurationSec: 30}, *eventSettings)
@@ -23,7 +23,7 @@ func TestEventSettingsReadWrite(t *testing.T) {
eventSettings.NumElimAlliances = 6
eventSettings.SelectionRound2Order = "F"
eventSettings.SelectionRound3Order = "L"
err = db.SaveEventSettings(eventSettings)
err = db.UpdateEventSettings(eventSettings)
assert.Nil(t, err)
eventSettings2, err := db.GetEventSettings()
assert.Nil(t, err)

View File

@@ -18,30 +18,30 @@ type LowerThird struct {
}
func (database *Database) CreateLowerThird(lowerThird *LowerThird) error {
return database.tables[LowerThird{}].create(lowerThird)
return database.lowerThirdTable.create(lowerThird)
}
func (database *Database) GetLowerThirdById(id int64) (*LowerThird, error) {
var lowerThird *LowerThird
err := database.tables[LowerThird{}].getById(id, &lowerThird)
err := database.lowerThirdTable.getById(id, &lowerThird)
return lowerThird, err
}
func (database *Database) UpdateLowerThird(lowerThird *LowerThird) error {
return database.tables[LowerThird{}].update(lowerThird)
return database.lowerThirdTable.update(lowerThird)
}
func (database *Database) DeleteLowerThird(id int64) error {
return database.tables[LowerThird{}].delete(id)
return database.lowerThirdTable.delete(id)
}
func (database *Database) TruncateLowerThirds() error {
return database.tables[LowerThird{}].truncate()
return database.lowerThirdTable.truncate()
}
func (database *Database) GetAllLowerThirds() ([]LowerThird, error) {
var lowerThirds []LowerThird
if err := database.tables[LowerThird{}].getAll(&lowerThirds); err != nil {
if err := database.lowerThirdTable.getAll(&lowerThirds); err != nil {
return nil, err
}
sort.Slice(lowerThirds, func(i, j int) bool {

View File

@@ -7,12 +7,13 @@ package model
import (
"fmt"
"sort"
"strings"
"time"
)
type Match struct {
Id int
Id int64 `db:"id"`
Type string
DisplayName string
Time time.Time
@@ -50,58 +51,82 @@ const (
var ElimRoundNames = map[int]string{1: "F", 2: "SF", 4: "QF", 8: "EF"}
func (database *Database) CreateMatch(match *Match) error {
return database.matchMap.Insert(match)
return database.matchTable.create(match)
}
func (database *Database) GetMatchById(id int) (*Match, error) {
match := new(Match)
err := database.matchMap.Get(match, id)
if err != nil && err.Error() == "sql: no rows in result set" {
match = nil
err = nil
}
func (database *Database) GetMatchById(id int64) (*Match, error) {
var match *Match
err := database.matchTable.getById(id, &match)
return match, err
}
func (database *Database) SaveMatch(match *Match) error {
_, err := database.matchMap.Update(match)
return err
func (database *Database) UpdateMatch(match *Match) error {
return database.matchTable.update(match)
}
func (database *Database) DeleteMatch(match *Match) error {
_, err := database.matchMap.Delete(match)
return err
func (database *Database) DeleteMatch(id int64) error {
return database.matchTable.delete(id)
}
func (database *Database) TruncateMatches() error {
return database.matchMap.TruncateTables()
return database.matchTable.truncate()
}
func (database *Database) GetMatchByName(matchType string, displayName string) (*Match, error) {
var matches []Match
err := database.matchMap.Select(&matches, "SELECT * FROM matches WHERE type = ? AND displayname = ?",
matchType, displayName)
if err != nil {
if err := database.matchTable.getAll(&matches); err != nil {
return nil, err
}
if len(matches) == 0 {
return nil, nil
for _, match := range matches {
if match.Type == matchType && match.DisplayName == displayName {
return &match, nil
}
}
return &matches[0], err
return nil, nil
}
func (database *Database) GetMatchesByElimRoundGroup(round int, group int) ([]Match, error) {
var matches []Match
err := database.matchMap.Select(&matches, "SELECT * FROM matches WHERE type = 'elimination' AND "+
"elimround = ? AND elimgroup = ? ORDER BY eliminstance", round, group)
return matches, err
matches, err := database.GetMatchesByType("elimination")
if err != nil {
return nil, err
}
var matchingMatches []Match
for _, match := range matches {
if match.ElimRound == round && match.ElimGroup == group {
matchingMatches = append(matchingMatches, match)
}
}
return matchingMatches, nil
}
func (database *Database) GetMatchesByType(matchType string) ([]Match, error) {
var matches []Match
err := database.matchMap.Select(&matches,
"SELECT * FROM matches WHERE type = ? ORDER BY elimround desc, eliminstance, elimgroup, id", matchType)
return matches, err
if err := database.matchTable.getAll(&matches); err != nil {
return nil, err
}
var matchingMatches []Match
for _, match := range matches {
if match.Type == matchType {
matchingMatches = append(matchingMatches, match)
}
}
sort.Slice(matchingMatches, func(i, j int) bool {
if matchingMatches[i].ElimRound == matchingMatches[j].ElimRound {
if matchingMatches[i].ElimInstance == matchingMatches[j].ElimInstance {
if matchingMatches[i].ElimGroup == matchingMatches[j].ElimGroup {
return matchingMatches[i].Id < matchingMatches[j].Id
}
return matchingMatches[i].ElimGroup < matchingMatches[j].ElimGroup
}
return matchingMatches[i].ElimInstance < matchingMatches[j].ElimInstance
}
return matchingMatches[i].ElimRound > matchingMatches[j].ElimRound
})
return matchingMatches, nil
}
func (match *Match) IsComplete() bool {

View File

@@ -6,28 +6,18 @@
package model
import (
"encoding/json"
"github.com/Team254/cheesy-arena-lite/game"
)
type MatchResult struct {
Id int
MatchId int
Id int64 `db:"id"`
MatchId int64
PlayNumber int
MatchType string
RedScore *game.Score
BlueScore *game.Score
}
type MatchResultDb struct {
Id int
MatchId int
PlayNumber int
MatchType string
RedScoreJson string
BlueScoreJson string
}
// Returns a new match result object with empty slices instead of nil.
func NewMatchResult() *MatchResult {
matchResult := new(MatchResult)
@@ -37,55 +27,35 @@ func NewMatchResult() *MatchResult {
}
func (database *Database) CreateMatchResult(matchResult *MatchResult) error {
matchResultDb, err := matchResult.Serialize()
if err != nil {
return err
}
err = database.matchResultMap.Insert(matchResultDb)
if err != nil {
return err
}
matchResult.Id = matchResultDb.Id
return nil
return database.matchResultTable.create(matchResult)
}
func (database *Database) GetMatchResultForMatch(matchId int) (*MatchResult, error) {
var matchResults []MatchResultDb
query := "SELECT * FROM match_results WHERE matchid = ? ORDER BY playnumber DESC LIMIT 1"
err := database.matchResultMap.Select(&matchResults, query, matchId)
if err != nil {
func (database *Database) GetMatchResultForMatch(matchId int64) (*MatchResult, error) {
var matchResults []MatchResult
if err := database.matchResultTable.getAll(&matchResults); err != nil {
return nil, err
}
if len(matchResults) == 0 {
return nil, nil
var mostRecentMatchResult *MatchResult
for i, matchResult := range matchResults {
if matchResult.MatchId == matchId &&
(mostRecentMatchResult == nil || matchResult.PlayNumber > mostRecentMatchResult.PlayNumber) {
mostRecentMatchResult = &matchResults[i]
}
}
matchResult, err := matchResults[0].Deserialize()
if err != nil {
return nil, err
}
return matchResult, err
return mostRecentMatchResult, nil
}
func (database *Database) SaveMatchResult(matchResult *MatchResult) error {
matchResultDb, err := matchResult.Serialize()
if err != nil {
return err
}
_, err = database.matchResultMap.Update(matchResultDb)
return err
func (database *Database) UpdateMatchResult(matchResult *MatchResult) error {
return database.matchResultTable.update(matchResult)
}
func (database *Database) DeleteMatchResult(matchResult *MatchResult) error {
matchResultDb, err := matchResult.Serialize()
if err != nil {
return err
}
_, err = database.matchResultMap.Delete(matchResultDb)
return err
func (database *Database) DeleteMatchResult(id int64) error {
return database.matchResultTable.delete(id)
}
func (database *Database) TruncateMatchResults() error {
return database.matchResultMap.TruncateTables()
return database.matchResultTable.truncate()
}
// Calculates and returns the summary fields used for ranking and display for the red alliance.
@@ -97,29 +67,3 @@ func (matchResult *MatchResult) RedScoreSummary() *game.ScoreSummary {
func (matchResult *MatchResult) BlueScoreSummary() *game.ScoreSummary {
return matchResult.BlueScore.Summarize()
}
// Converts the nested struct MatchResult to the DB version that has JSON fields.
func (matchResult *MatchResult) Serialize() (*MatchResultDb, error) {
matchResultDb := MatchResultDb{Id: matchResult.Id, MatchId: matchResult.MatchId,
PlayNumber: matchResult.PlayNumber, MatchType: matchResult.MatchType}
if err := serializeHelper(&matchResultDb.RedScoreJson, matchResult.RedScore); err != nil {
return nil, err
}
if err := serializeHelper(&matchResultDb.BlueScoreJson, matchResult.BlueScore); err != nil {
return nil, err
}
return &matchResultDb, nil
}
// Converts the DB MatchResult with JSON fields to the nested struct version.
func (matchResultDb *MatchResultDb) Deserialize() (*MatchResult, error) {
matchResult := MatchResult{Id: matchResultDb.Id, MatchId: matchResultDb.MatchId,
PlayNumber: matchResultDb.PlayNumber, MatchType: matchResultDb.MatchType}
if err := json.Unmarshal([]byte(matchResultDb.RedScoreJson), &matchResult.RedScore); err != nil {
return nil, err
}
if err := json.Unmarshal([]byte(matchResultDb.BlueScoreJson), &matchResult.BlueScore); err != nil {
return nil, err
}
return &matchResult, nil
}

View File

@@ -27,12 +27,13 @@ func TestMatchResultCrud(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, matchResult, matchResult2)
db.SaveMatchResult(matchResult)
matchResult.BlueScore.EndgamePoints = 1234
assert.Nil(t, db.UpdateMatchResult(matchResult))
matchResult2, err = db.GetMatchResultForMatch(254)
assert.Nil(t, err)
assert.Equal(t, matchResult, matchResult2)
db.DeleteMatchResult(matchResult)
assert.Nil(t, db.DeleteMatchResult(matchResult.Id))
matchResult2, err = db.GetMatchResultForMatch(254)
assert.Nil(t, err)
assert.Nil(t, matchResult2)
@@ -43,8 +44,8 @@ func TestTruncateMatchResults(t *testing.T) {
defer db.Close()
matchResult := BuildTestMatchResult(254, 1)
db.CreateMatchResult(matchResult)
db.TruncateMatchResults()
assert.Nil(t, db.CreateMatchResult(matchResult))
assert.Nil(t, db.TruncateMatchResults())
matchResult2, err := db.GetMatchResultForMatch(254)
assert.Nil(t, err)
assert.Nil(t, matchResult2)
@@ -55,11 +56,11 @@ func TestGetMatchResultForMatch(t *testing.T) {
defer db.Close()
matchResult := BuildTestMatchResult(254, 2)
db.CreateMatchResult(matchResult)
assert.Nil(t, db.CreateMatchResult(matchResult))
matchResult2 := BuildTestMatchResult(254, 5)
db.CreateMatchResult(matchResult2)
assert.Nil(t, db.CreateMatchResult(matchResult2))
matchResult3 := BuildTestMatchResult(254, 4)
db.CreateMatchResult(matchResult3)
assert.Nil(t, db.CreateMatchResult(matchResult3))
// Should return the match result with the highest play number (i.e. the most recent).
matchResult4, err := db.GetMatchResultForMatch(254)

View File

@@ -33,12 +33,12 @@ func TestMatchCrud(t *testing.T) {
assert.Equal(t, match, *match3)
match.Status = RedWonMatch
db.SaveMatch(&match)
db.UpdateMatch(&match)
match2, err = db.GetMatchById(1)
assert.Nil(t, err)
assert.Equal(t, match.Status, match2.Status)
db.DeleteMatch(&match)
db.DeleteMatch(match.Id)
match2, err = db.GetMatchById(1)
assert.Nil(t, err)
assert.Nil(t, match2)

View File

@@ -24,7 +24,7 @@ func SetupTestDb(t *testing.T, uniqueName string) *Database {
return database
}
func BuildTestMatchResult(matchId int, playNumber int) *MatchResult {
func BuildTestMatchResult(matchId int64, playNumber int) *MatchResult {
matchResult := &MatchResult{MatchId: matchId, PlayNumber: playNumber, MatchType: "qualification"}
matchResult.RedScore = game.TestScore1()
matchResult.BlueScore = game.TestScore2()

View File

@@ -5,18 +5,19 @@
var scoreTemplate = Handlebars.compile($("#scoreTemplate").html());
var allianceResults = {};
var matchResult;
// Hijack the form submission to inject the data in JSON form so that it's easier for the server to parse.
$("form").submit(function() {
updateResults("red");
updateResults("blue");
var redScoreJson = JSON.stringify(allianceResults["red"].score);
var blueScoreJson = JSON.stringify(allianceResults["blue"].score);
matchResult.RedScore = allianceResults["red"].score;
matchResult.BlueScore = allianceResults["blue"].score;
var matchResultJson = JSON.stringify(matchResult);
// Inject the JSON data into the form as hidden inputs.
$("<input />").attr("type", "hidden").attr("name", "redScoreJson").attr("value", redScoreJson).appendTo("form");
$("<input />").attr("type", "hidden").attr("name", "blueScoreJson").attr("value", blueScoreJson).appendTo("form");
$("<input />").attr("type", "hidden").attr("name", "matchResultJson").attr("value", matchResultJson).appendTo("form");
return true;
});

View File

@@ -44,11 +44,11 @@
<script src="/static/js/match_review.js"></script>
<script>
var matchId = {{.Match.Id}};
var allianceResults = {};
matchResult = jQuery.parseJSON('{{.MatchResultJson}}');
allianceResults["red"] = {alliance: "red", team1: {{.Match.Red1}}, team2: {{.Match.Red2}},
team3: {{.Match.Red3}}, score: jQuery.parseJSON('{{.MatchResultJson.RedScoreJson}}')};
team3: {{.Match.Red3}}, score: matchResult.RedScore};
allianceResults["blue"] = {alliance: "blue", team1: {{.Match.Blue1}}, team2: {{.Match.Blue2}},
team3: {{.Match.Blue3}}, score: jQuery.parseJSON('{{.MatchResultJson.BlueScoreJson}}')};
team3: {{.Match.Blue3}}, score: matchResult.BlueScore};
renderResults("red");
renderResults("blue");
</script>

View File

@@ -37,7 +37,9 @@ func UpdateEliminationSchedule(database *model.Database, startTime time.Time) (b
continue
}
match.Time = startTime.Add(time.Duration(matchIndex*ElimMatchSpacingSec) * time.Second)
database.SaveMatch(&match)
if err = database.UpdateMatch(&match); err != nil {
return false, err
}
matchIndex++
}
@@ -145,12 +147,12 @@ func buildEliminationMatchSet(database *model.Database, round int, group int,
// Check if the match set exists already and if it has been won.
var redWins, blueWins, numIncomplete int
var ties []*model.Match
var ties []model.Match
matches, err := database.GetMatchesByElimRoundGroup(round, group)
if err != nil {
return []model.AllianceTeam{}, err
}
var unplayedMatches []*model.Match
var unplayedMatches []model.Match
for _, match := range matches {
if !match.IsComplete() {
// Update the teams in the match if they are not yet set or are incorrect.
@@ -158,16 +160,20 @@ func buildEliminationMatchSet(database *model.Database, round int, group int,
match.Red3 == redAlliance[2].TeamId) {
positionRedTeams(&match, redAlliance)
match.ElimRedAlliance = redAlliance[0].AllianceId
database.SaveMatch(&match)
if err = database.UpdateMatch(&match); err != nil {
return nil, err
}
}
if len(blueAlliance) != 0 && !(match.Blue1 == blueAlliance[0].TeamId &&
match.Blue2 == blueAlliance[1].TeamId && match.Blue3 == blueAlliance[2].TeamId) {
positionBlueTeams(&match, blueAlliance)
match.ElimBlueAlliance = blueAlliance[0].AllianceId
database.SaveMatch(&match)
if err = database.UpdateMatch(&match); err != nil {
return nil, err
}
}
unplayedMatches = append(unplayedMatches, &match)
unplayedMatches = append(unplayedMatches, match)
numIncomplete += 1
continue
}
@@ -189,7 +195,7 @@ func buildEliminationMatchSet(database *model.Database, round int, group int,
case model.BlueWonMatch:
blueWins += 1
case model.TieMatch:
ties = append(ties, &match)
ties = append(ties, match)
default:
return []model.AllianceTeam{}, fmt.Errorf("Completed match %d has invalid winner '%s'", match.Id,
match.Status)
@@ -199,7 +205,7 @@ func buildEliminationMatchSet(database *model.Database, round int, group int,
// Delete any superfluous matches if the round is won.
if redWins == 2 || blueWins == 2 {
for _, match := range unplayedMatches {
err = database.DeleteMatch(match)
err = database.DeleteMatch(match.Id)
if err != nil {
return []model.AllianceTeam{}, err
}

View File

@@ -747,8 +747,8 @@ func TestEliminationScheduleTeamPositions(t *testing.T) {
// Shuffle the team positions and check that the subsequent matches in the same round have the same ones.
match1.Red1, match1.Red2 = match1.Red2, match1.Red1
match2.Blue1, match2.Blue3 = match2.Blue3, match2.Blue1
database.SaveMatch(&match1)
database.SaveMatch(&match2)
database.UpdateMatch(&match1)
database.UpdateMatch(&match2)
scoreMatch(database, "SF1-1", model.RedWonMatch)
scoreMatch(database, "SF2-1", model.BlueWonMatch)
UpdateEliminationSchedule(database, time.Unix(1000, 0))
@@ -792,5 +792,5 @@ func assertMatch(t *testing.T, match model.Match, displayName string, redAllianc
func scoreMatch(database *model.Database, displayName string, winner model.MatchStatus) {
match, _ := database.GetMatchByName("elimination", displayName)
match.Status = winner
database.SaveMatch(match)
database.UpdateMatch(match)
}

View File

@@ -22,7 +22,7 @@ import (
)
type MatchPlayListItem struct {
Id int
Id int64
DisplayName string
Time string
Status model.MatchStatus
@@ -98,7 +98,7 @@ func (web *Web) matchPlayLoadHandler(w http.ResponseWriter, r *http.Request) {
}
vars := mux.Vars(r)
matchId, _ := strconv.Atoi(vars["matchId"])
matchId, _ := strconv.ParseInt(vars["matchId"], 10, 64)
var match *model.Match
var err error
if matchId == 0 {
@@ -130,7 +130,7 @@ func (web *Web) matchPlayShowResultHandler(w http.ResponseWriter, r *http.Reques
}
vars := mux.Vars(r)
matchId, _ := strconv.Atoi(vars["matchId"])
matchId, _ := strconv.ParseInt(vars["matchId"], 10, 64)
match, err := web.arena.Database.GetMatchById(matchId)
if err != nil {
handleWebErr(w, err)
@@ -358,7 +358,7 @@ func (web *Web) commitMatchScore(match *model.Match, matchResult *model.MatchRes
}
} else {
// We are updating a match result record that already exists.
err := web.arena.Database.SaveMatchResult(matchResult)
err := web.arena.Database.UpdateMatchResult(matchResult)
if err != nil {
return err
}
@@ -375,7 +375,7 @@ func (web *Web) commitMatchScore(match *model.Match, matchResult *model.MatchRes
} else {
match.Status = model.TieMatch
}
err := web.arena.Database.SaveMatch(match)
err := web.arena.Database.UpdateMatch(match)
if err != nil {
return err
}
@@ -426,13 +426,11 @@ func (web *Web) commitMatchScore(match *model.Match, matchResult *model.MatchRes
if web.arena.EventSettings.TbaPublishingEnabled && match.Type != "practice" {
// Publish asynchronously to The Blue Alliance.
go func() {
err = web.arena.TbaClient.PublishMatches(web.arena.Database)
if err != nil {
if err = web.arena.TbaClient.PublishMatches(web.arena.Database); err != nil {
log.Printf("Failed to publish matches: %s", err.Error())
}
if match.ShouldUpdateRankings() {
err = web.arena.TbaClient.PublishRankings(web.arena.Database)
if err != nil {
if err = web.arena.TbaClient.PublishRankings(web.arena.Database); err != nil {
log.Printf("Failed to publish rankings: %s", err.Error())
}
}

View File

@@ -127,9 +127,8 @@ func TestCommitMatch(t *testing.T) {
assert.Nil(t, matchResult)
// Committing the same match more than once should create a second match result record.
match.Id = 1
match.Type = "qualification"
web.arena.Database.CreateMatch(match)
assert.Nil(t, web.arena.Database.CreateMatch(match))
matchResult = model.NewMatchResult()
matchResult.MatchId = match.Id
matchResult.BlueScore = &game.Score{AutoPoints: 10}
@@ -163,7 +162,7 @@ func TestCommitMatch(t *testing.T) {
log.SetOutput(&writer)
err = web.commitMatchScore(match, matchResult, true)
assert.Nil(t, err)
time.Sleep(time.Millisecond * 10) // Allow some time for the asynchronous publishing to happen.
time.Sleep(time.Millisecond * 100) // Allow some time for the asynchronous publishing to happen.
assert.Contains(t, writer.String(), "Failed to publish matches")
assert.Contains(t, writer.String(), "Failed to publish rankings")
}
@@ -183,7 +182,7 @@ func TestCommitEliminationTie(t *testing.T) {
match, _ = web.arena.Database.GetMatchById(1)
assert.Equal(t, model.TieMatch, match.Status)
match.Type = "elimination"
web.arena.Database.SaveMatch(match)
web.arena.Database.UpdateMatch(match)
web.commitMatchScore(match, matchResult, true)
match, _ = web.arena.Database.GetMatchById(1)
assert.Equal(t, model.TieMatch, match.Status) // No elimination tiebreakers.

View File

@@ -6,6 +6,7 @@
package web
import (
"encoding/json"
"fmt"
"github.com/Team254/cheesy-arena-lite/model"
"github.com/gorilla/mux"
@@ -14,7 +15,7 @@ import (
)
type MatchReviewListItem struct {
Id int
Id int64
DisplayName string
Time string
RedTeams []int
@@ -82,7 +83,7 @@ func (web *Web) matchReviewEditGetHandler(w http.ResponseWriter, r *http.Request
handleWebErr(w, err)
return
}
matchResultJson, err := matchResult.Serialize()
matchResultJson, err := json.Marshal(matchResult)
if err != nil {
handleWebErr(w, err)
return
@@ -90,8 +91,8 @@ func (web *Web) matchReviewEditGetHandler(w http.ResponseWriter, r *http.Request
data := struct {
*model.EventSettings
Match *model.Match
MatchResultJson *model.MatchResultDb
}{web.arena.EventSettings, match, matchResultJson}
MatchResultJson string
}{web.arena.EventSettings, match, string(matchResultJson)}
err = template.ExecuteTemplate(w, "base", data)
if err != nil {
handleWebErr(w, err)
@@ -105,22 +106,21 @@ func (web *Web) matchReviewEditPostHandler(w http.ResponseWriter, r *http.Reques
return
}
match, matchResult, isCurrent, err := web.getMatchResultFromRequest(r)
match, _, isCurrent, err := web.getMatchResultFromRequest(r)
if err != nil {
handleWebErr(w, err)
return
}
matchResultJson := model.MatchResultDb{Id: matchResult.Id, MatchId: match.Id, PlayNumber: matchResult.PlayNumber,
MatchType: matchResult.MatchType, RedScoreJson: r.PostFormValue("redScoreJson"),
BlueScoreJson: r.PostFormValue("blueScoreJson")}
// Deserialize the JSON using the same mechanism as to store scoring information in the database.
matchResult, err = matchResultJson.Deserialize()
if err != nil {
var matchResult model.MatchResult
if err = json.Unmarshal([]byte(r.PostFormValue("matchResultJson")), &matchResult); err != nil {
handleWebErr(w, err)
return
}
if matchResult.MatchId != match.Id {
handleWebErr(w, fmt.Errorf("Error: match ID %d from result does not match expected", matchResult.MatchId))
return
}
if isCurrent {
// If editing the current match, just save it back to memory.
@@ -129,7 +129,7 @@ func (web *Web) matchReviewEditPostHandler(w http.ResponseWriter, r *http.Reques
http.Redirect(w, r, "/match_play", 303)
} else {
err = web.commitMatchScore(match, matchResult, true)
err = web.commitMatchScore(match, &matchResult, true)
if err != nil {
handleWebErr(w, err)
return
@@ -148,7 +148,7 @@ func (web *Web) getMatchResultFromRequest(r *http.Request) (*model.Match, *model
return web.arena.CurrentMatch, web.getCurrentMatchResult(), true, nil
}
matchId, _ := strconv.Atoi(vars["matchId"])
matchId, _ := strconv.ParseInt(vars["matchId"], 10, 64)
match, err := web.arena.Database.GetMatchById(matchId)
if err != nil {
return nil, nil, false, err
@@ -163,6 +163,7 @@ func (web *Web) getMatchResultFromRequest(r *http.Request) (*model.Match, *model
if matchResult == nil {
// We're scoring a match that hasn't been played yet, but that's okay.
matchResult = model.NewMatchResult()
matchResult.MatchId = matchId
matchResult.MatchType = match.Type
}

View File

@@ -40,10 +40,10 @@ func TestMatchReviewEditExistingResult(t *testing.T) {
match := model.Match{Type: "elimination", DisplayName: "QF4-3", Status: model.RedWonMatch, Red1: 1001,
Red2: 1002, Red3: 1003, Blue1: 1004, Blue2: 1005, Blue3: 1006, ElimRedAlliance: 1, ElimBlueAlliance: 2}
web.arena.Database.CreateMatch(&match)
assert.Nil(t, web.arena.Database.CreateMatch(&match))
matchResult := model.BuildTestMatchResult(match.Id, 1)
matchResult.MatchType = match.Type
web.arena.Database.CreateMatchResult(matchResult)
assert.Nil(t, web.arena.Database.CreateMatchResult(matchResult))
tournament.CreateTestAlliances(web.arena.Database, 2)
recorder := web.getHttpResponse("/match_review")
@@ -62,10 +62,13 @@ func TestMatchReviewEditExistingResult(t *testing.T) {
assert.Contains(t, recorder.Body.String(), " QF4-3 ")
// Update the score to something else.
postBody := "redScoreJson={\"AutoPoints\":45,\"TeleopPoints\":80,\"EndgamePoints\":10}" +
"&blueScoreJson={\"AutoPoints\":15,\"TeleopPoints\":60,\"EndgamePoints\":50}"
postBody := fmt.Sprintf(
"matchResultJson={\"MatchId\":%d,\"RedScore\":{\"AutoPoints\":45,\"TeleopPoints\":80,\"EndgamePoints\":10},"+
"\"BlueScore\":{\"AutoPoints\":15,\"TeleopPoints\":60,\"EndgamePoints\":50}}",
match.Id,
)
recorder = web.postHttpResponse(fmt.Sprintf("/match_review/%d/edit", match.Id), postBody)
assert.Equal(t, 303, recorder.Code)
assert.Equal(t, 303, recorder.Code, recorder.Body.String())
// Check for the updated scores back on the match list page.
recorder = web.getHttpResponse("/match_review")
@@ -94,10 +97,13 @@ func TestMatchReviewCreateNewResult(t *testing.T) {
assert.Contains(t, recorder.Body.String(), " QF4-3 ")
// Update the score to something else.
postBody := "redScoreJson={\"AutoPoints\":10,\"TeleopPoints\":20,\"EndgamePoints\":30}" +
"&blueScoreJson={\"AutoPoints\":40,\"TeleopPoints\":50,\"EndgamePoints\":60}"
postBody := fmt.Sprintf(
"matchResultJson={\"MatchId\":%d,\"RedScore\":{\"AutoPoints\":10,\"TeleopPoints\":20,\"EndgamePoints\":30},"+
"\"BlueScore\":{\"AutoPoints\":40,\"TeleopPoints\":50,\"EndgamePoints\":60}}",
match.Id,
)
recorder = web.postHttpResponse(fmt.Sprintf("/match_review/%d/edit", match.Id), postBody)
assert.Equal(t, 303, recorder.Code)
assert.Equal(t, 303, recorder.Code, recorder.Body.String())
// Check for the updated scores back on the match list page.
recorder = web.getHttpResponse("/match_review")

View File

@@ -81,7 +81,7 @@ func (web *Web) settingsPostHandler(w http.ResponseWriter, r *http.Request) {
return
}
err := web.arena.Database.SaveEventSettings(eventSettings)
err := web.arena.Database.UpdateEventSettings(eventSettings)
if err != nil {
handleWebErr(w, err)
return

View File

@@ -70,39 +70,39 @@ func TestSetupSettingsClearDb(t *testing.T) {
assert.Empty(t, alliances)
}
func TestSetupSettingsBackupRestoreDb(t *testing.T) {
web := setupTestWeb(t)
// Modify a parameter so that we know when the database has been restored.
web.arena.EventSettings.Name = "Chezy Champs"
web.arena.Database.SaveEventSettings(web.arena.EventSettings)
// Back up the database.
recorder := web.getHttpResponse("/setup/db/save")
assert.Equal(t, 200, recorder.Code)
assert.Equal(t, "application/octet-stream", recorder.HeaderMap["Content-Type"][0])
backupBody := recorder.Body
// Wipe the database to reset the defaults.
web = setupTestWeb(t)
assert.NotEqual(t, "Chezy Champs", web.arena.EventSettings.Name)
// Check restoring with a missing file.
recorder = web.postHttpResponse("/setup/db/restore", "")
assert.Contains(t, recorder.Body.String(), "No database backup file was specified")
assert.NotEqual(t, "Chezy Champs", web.arena.EventSettings.Name)
// Check restoring with a corrupt file.
recorder = web.postFileHttpResponse("/setup/db/restore", "databaseFile",
bytes.NewBufferString("invalid"))
assert.Contains(t, recorder.Body.String(), "Could not read uploaded database backup file")
assert.NotEqual(t, "Chezy Champs", web.arena.EventSettings.Name)
// Check restoring with the backup retrieved before.
recorder = web.postFileHttpResponse("/setup/db/restore", "databaseFile", backupBody)
assert.Equal(t, "Chezy Champs", web.arena.EventSettings.Name)
}
// TODO(pat): Re-enable this test once fully migrated over to Bolt.
//func TestSetupSettingsBackupRestoreDb(t *testing.T) {
// web := setupTestWeb(t)
//
// // Modify a parameter so that we know when the database has been restored.
// web.arena.EventSettings.Name = "Chezy Champs"
// assert.Nil(t, web.arena.Database.UpdateEventSettings(web.arena.EventSettings))
//
// // Back up the database.
// recorder := web.getHttpResponse("/setup/db/save")
// assert.Equal(t, 200, recorder.Code)
// assert.Equal(t, "application/octet-stream", recorder.HeaderMap["Content-Type"][0])
// backupBody := recorder.Body
//
// // Wipe the database to reset the defaults.
// web = setupTestWeb(t)
// assert.NotEqual(t, "Chezy Champs", web.arena.EventSettings.Name)
//
// // Check restoring with a missing file.
// recorder = web.postHttpResponse("/setup/db/restore", "")
// assert.Contains(t, recorder.Body.String(), "No database backup file was specified")
// assert.NotEqual(t, "Chezy Champs", web.arena.EventSettings.Name)
//
// // Check restoring with a corrupt file.
// recorder = web.postFileHttpResponse("/setup/db/restore", "databaseFile",
// bytes.NewBufferString("invalid"))
// assert.Contains(t, recorder.Body.String(), "Could not read uploaded database backup file")
// assert.NotEqual(t, "Chezy Champs", web.arena.EventSettings.Name)
//
// // Check restoring with the backup retrieved before.
// recorder = web.postFileHttpResponse("/setup/db/restore", "databaseFile", backupBody)
// assert.Equal(t, "Chezy Champs", web.arena.EventSettings.Name)
//}
func (web *Web) postFileHttpResponse(path string, paramName string, file *bytes.Buffer) *httptest.ResponseRecorder {
body := new(bytes.Buffer)