From 1774197c13e23b1236a4834555fea190de2f0ccd Mon Sep 17 00:00:00 2001 From: Sam Baumgarten Date: Wed, 1 Apr 2015 14:19:14 -0700 Subject: [PATCH] Add TBA API for team info and awards --- .../20150324201112_AddFRCEventsAPIColumns.sql | 4 - .../20150401132914_AddTBAColumns.sql | 3 + event_settings.go | 48 +++++----- event_settings_test.go | 2 +- setup_settings.go | 5 +- setup_teams.go | 92 ++++++------------- tba.go | 75 +++++++++++++++ templates/setup_settings.html | 18 ++-- templates/setup_teams.html | 2 +- 9 files changed, 142 insertions(+), 107 deletions(-) delete mode 100644 db/migrations/20150324201112_AddFRCEventsAPIColumns.sql create mode 100644 db/migrations/20150401132914_AddTBAColumns.sql diff --git a/db/migrations/20150324201112_AddFRCEventsAPIColumns.sql b/db/migrations/20150324201112_AddFRCEventsAPIColumns.sql deleted file mode 100644 index 294d966..0000000 --- a/db/migrations/20150324201112_AddFRCEventsAPIColumns.sql +++ /dev/null @@ -1,4 +0,0 @@ --- +goose Up -ALTER TABLE event_settings ADD COLUMN fmsapidownloadenabled BOOLEAN; -ALTER TABLE event_settings ADD COLUMN fmsapiusername VARCHAR(255); -ALTER TABLE event_settings ADD COLUMN fmsapiauthkey VARCHAR(255); diff --git a/db/migrations/20150401132914_AddTBAColumns.sql b/db/migrations/20150401132914_AddTBAColumns.sql new file mode 100644 index 0000000..89d6c6a --- /dev/null +++ b/db/migrations/20150401132914_AddTBAColumns.sql @@ -0,0 +1,3 @@ +-- +goose Up +ALTER TABLE event_settings ADD COLUMN tbadownloadenabled BOOLEAN; +ALTER TABLE event_settings ADD COLUMN tbaawardsdownloadenabled BOOLEAN; \ No newline at end of file diff --git a/event_settings.go b/event_settings.go index 986dbab..f313c64 100644 --- a/event_settings.go +++ b/event_settings.go @@ -6,29 +6,28 @@ package main type EventSettings struct { - Id int - Name string - Code string - DisplayBackgroundColor string - NumElimAlliances int - SelectionRound2Order string - SelectionRound3Order string - FMSAPIDownloadEnabled bool - FMSAPIUsername string - FMSAPIAuthKey string - AllianceDisplayHotGoals bool - RedGoalLightsAddress string - BlueGoalLightsAddress string - TbaPublishingEnabled bool - TbaEventCode string - TbaSecretId string - TbaSecret string - NetworkSecurityEnabled bool - ApAddress string - ApUsername string - ApPassword string - SwitchAddress string - SwitchPassword string + Id int + Name string + Code string + DisplayBackgroundColor string + NumElimAlliances int + SelectionRound2Order string + SelectionRound3Order string + TBADownloadEnabled bool + TBAAwardsDownloadEnabled bool + AllianceDisplayHotGoals bool + RedGoalLightsAddress string + BlueGoalLightsAddress string + TbaPublishingEnabled bool + TbaEventCode string + TbaSecretId string + TbaSecret string + NetworkSecurityEnabled bool + ApAddress string + ApUsername string + ApPassword string + SwitchAddress string + SwitchPassword string } const eventSettingsId = 0 @@ -44,7 +43,8 @@ func (database *Database) GetEventSettings() (*EventSettings, error) { eventSettings.NumElimAlliances = 8 eventSettings.SelectionRound2Order = "L" eventSettings.SelectionRound3Order = "" - eventSettings.FMSAPIDownloadEnabled = false + eventSettings.TBADownloadEnabled = true + eventSettings.TBAAwardsDownloadEnabled = true err = database.eventSettingsMap.Insert(eventSettings) if err != nil { return nil, err diff --git a/event_settings_test.go b/event_settings_test.go index 44dd997..65f89c8 100644 --- a/event_settings_test.go +++ b/event_settings_test.go @@ -19,7 +19,7 @@ func TestEventSettingsReadWrite(t *testing.T) { assert.Nil(t, err) assert.Equal(t, EventSettings{Id: 0, Name: "Untitled Event", Code: "UE", DisplayBackgroundColor: "#00ff00", NumElimAlliances: 8, SelectionRound2Order: "L", SelectionRound3Order: "", - FMSAPIDownloadEnabled: false}, *eventSettings) + TBADownloadEnabled: true, TBAAwardsDownloadEnabled: true}, *eventSettings) eventSettings.Name = "Chezy Champs" eventSettings.Code = "cc" diff --git a/setup_settings.go b/setup_settings.go index a426e40..4b1938d 100644 --- a/setup_settings.go +++ b/setup_settings.go @@ -42,9 +42,8 @@ func SettingsPostHandler(w http.ResponseWriter, r *http.Request) { eventSettings.NumElimAlliances = numAlliances eventSettings.SelectionRound2Order = r.PostFormValue("selectionRound2Order") eventSettings.SelectionRound3Order = r.PostFormValue("selectionRound3Order") - eventSettings.FMSAPIDownloadEnabled = r.PostFormValue("FMSAPIDownloadEnabled") == "on" - eventSettings.FMSAPIUsername = r.PostFormValue("FMSAPIUsername") - eventSettings.FMSAPIAuthKey = r.PostFormValue("FMSAPIAuthKey") + eventSettings.TBADownloadEnabled = r.PostFormValue("TBADownloadEnabled") == "on" + eventSettings.TBAAwardsDownloadEnabled = r.PostFormValue("TBAAwardsDownloadEnabled") == "on" eventSettings.AllianceDisplayHotGoals = r.PostFormValue("allianceDisplayHotGoals") == "on" eventSettings.RedGoalLightsAddress = r.PostFormValue("redGoalLightsAddress") eventSettings.BlueGoalLightsAddress = r.PostFormValue("blueGoalLightsAddress") diff --git a/setup_teams.go b/setup_teams.go index 07b1fec..a5e76ad 100644 --- a/setup_teams.go +++ b/setup_teams.go @@ -6,12 +6,12 @@ package main import ( - "encoding/json" "fmt" + "time" + "bytes" "github.com/dchest/uniuri" "github.com/gorilla/mux" "html/template" - "io/ioutil" "net/http" "strconv" "strings" @@ -19,7 +19,7 @@ import ( const wpaKeyLength = 8 -var officialTeamInfoUrl = "https://frc-api.usfirst.org/api/v1.0/teams/2015" +var officialTeamInfoUrl = "http://www.thebluealliance.com/api/v2/team/" // Shows the team list. func TeamsGetHandler(w http.ResponseWriter, r *http.Request) { @@ -233,71 +233,39 @@ func canModifyTeamList() bool { func getOfficialTeamInfo(teamId int) (*Team, error) { // Create the team variable that stores the result var team Team - + // If team info download is enabled, download the current teams data (caching isn't easy with the new paging system in the api) - if eventSettings.FMSAPIDownloadEnabled && eventSettings.FMSAPIUsername != "" && eventSettings.FMSAPIAuthKey != "" { - // Make an HTTP GET request with basic auth to get the info - client := &http.Client{} - var url = officialTeamInfoUrl + "?teamNumber=" + strconv.Itoa(teamId); - req, err := http.NewRequest("GET", url, nil) - req.SetBasicAuth(eventSettings.FMSAPIUsername, eventSettings.FMSAPIAuthKey) - resp, err := client.Do(req) - - // Handle any errors - if err != nil { - return nil, err - } + if eventSettings.TBADownloadEnabled { + var tbaTeam *TbaTeam = getTeamFromTba(teamId) - // Get the response and handle errors - defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - - // Parse the response into an object - var respJSON map[string]interface{} - json.Unmarshal(body, &respJSON) - - // Check if the result is valid. If a team is not found it won't be allowing us to just return a basic team - if respJSON == nil { - team = Team{Id: teamId} + // Check if the result is valid. If a team is not found, just return a basic team + if tbaTeam == nil { + team = Team{Id: teamId} return &team, nil - } - - // Break out teams array - var teams = respJSON["teams"].([]interface{}) - - // Get the first team returned - var teamData = teams[0].(map[string]interface {}) + } - // Put all the info into variables (to allow for validation) - var name string - var nickname string - var city string - var stateProv string - var country string - var rookieYear int - var robotName string - - if teamData["nameFull"] != nil { name = teamData["nameFull"].(string) } - if teamData["nameShort"] != nil { nickname = teamData["nameShort"].(string) } - if teamData["city"] != nil { city = teamData["city"].(string) } - if teamData["stateProv"] != nil { stateProv = teamData["stateProv"].(string) } - if teamData["country"] != nil { country = teamData["country"].(string) } - if teamData["rookieYear"] != nil { rookieYear = int(teamData["rookieYear"].(float64)) } - if teamData["robotName"] != nil { robotName = teamData["robotName"].(string) } + var recentAwards []TbaAward; + if eventSettings.TBAAwardsDownloadEnabled { + recentAwards = getTeamAwardsFromTba(teamId) + } + var accomplishmentsBuffer bytes.Buffer + + // Generate accomplishments string + for _, award := range recentAwards { + if time.Now().Year() - award.Year <= 2 { + accomplishmentsBuffer.WriteString(fmt.Sprint(award.Year, " - ", award.Name, "\n")) + } + } + // Use those variables to make a team object - team = Team{Id: teamId, Name: name, Nickname: nickname, - City: city, StateProv: stateProv, - Country: country, RookieYear: rookieYear, - RobotName: robotName} - } else { - // If team grab is disabled, just use the team number - team = Team{Id: teamId} - } + team = Team{Id: teamId, Name: tbaTeam.Name, Nickname: tbaTeam.Nickname, + City: tbaTeam.Locality, StateProv: tbaTeam.Reigon, + Country: tbaTeam.Country, RookieYear: tbaTeam.RookieYear, Accomplishments: accomplishmentsBuffer.String()} + } else { + // If team grab is disabled, just use the team number + team = Team{Id: teamId} + } // Return the team object return &team, nil diff --git a/tba.go b/tba.go index 2a0726d..4b7ece4 100644 --- a/tba.go +++ b/tba.go @@ -17,6 +17,8 @@ import ( var tbaBaseUrl = "http://www.thebluealliance.com" +// MODELS + type TbaMatch struct { CompLevel string `json:"comp_level"` SetNumber int `json:"set_number"` @@ -49,6 +51,65 @@ type TbaRanking struct { GoalFoul int `json:"G&F"` } +type TbaTeam struct { + Website string `json:"website"` + Name string `json:"name"` + Locality string `json:"locality"` + RookieYear int `json:"rookie_year"` + Reigon string `json:"region"` + TeamNumber int `json:"team_number"` + Location string `json:"location"` + Key string `json:"key"` + Country string `json:"country_name"` + Nickname string `json:"nickname"` +} + +type TbaAward struct { + Name string `json:"name"` + EventKey string `json:"event_key"` + Year int `json:"year"` + AwardType int `json:"award_type"` +} + +// DATA RETRIEVAL +func getTeamFromTba(teamNumber int) (*TbaTeam) { + url := fmt.Sprint("/api/v2/team/", string(getTbaTeam(teamNumber))) + resp, _ := getTbaRequest(url); + + + // Get the response and handle errors + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil + } + + var teamData TbaTeam + json.Unmarshal(body, &teamData) + + return &teamData +} + +func getTeamAwardsFromTba(teamNumber int) ([]TbaAward) { + url := fmt.Sprint("/api/v2/team/", string(getTbaTeam(teamNumber)), "/history/awards") + resp, _ := getTbaRequest(url); + + + // Get the response and handle errors + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil + } + + var awardData []TbaAward + json.Unmarshal(body, &awardData) + + return awardData +} + +// PUBLISHING + // Uploads the event team list to The Blue Alliance. func PublishTeams() error { teams, err := db.GetAllTeams() @@ -213,6 +274,8 @@ func getTbaTeam(team int) string { return fmt.Sprintf("frc%d", team) } +// HELPERS + // Signs the request and sends it to the TBA API. func postTbaRequest(resource string, body []byte) (*http.Response, error) { path := fmt.Sprintf("/api/trusted/v1/event/%s/%s/update", eventSettings.TbaEventCode, resource) @@ -227,3 +290,15 @@ func postTbaRequest(resource string, body []byte) (*http.Response, error) { request.Header.Add("X-TBA-Auth-Sig", signature) return client.Do(request) } + +// Sends a GET request to the TBA API +func getTbaRequest(path string) (*http.Response, error) { + // Make an HTTP GET request with the TBA auth headers + client := &http.Client{} + req, err := http.NewRequest("GET", fmt.Sprint(tbaBaseUrl, path), nil) + if err != nil { + return nil, err + } + req.Header.Set("X-TBA-App-Id", "cheesy-arena:cheesy-fms:v0.1") + return client.Do(req) +} diff --git a/templates/setup_settings.html b/templates/setup_settings.html index 57d0225..f5b3ecc 100644 --- a/templates/setup_settings.html +++ b/templates/setup_settings.html @@ -93,23 +93,17 @@
- FRC Events API + Automatic Team Info Download
- +
- +
- -
- -
-
-
- -
- + +
+
diff --git a/templates/setup_teams.html b/templates/setup_teams.html index 426974a..60c63ca 100644 --- a/templates/setup_teams.html +++ b/templates/setup_teams.html @@ -18,7 +18,7 @@
Import Teams - {{if not .EventSettings.FMSAPIDownloadEnabled}}

To automatically download data about teams, enter your FRC Events API key on the event settings page

{{end}} + {{if not .EventSettings.TBADownloadEnabled}}

To automatically download data about teams, enable TBA Team Info Download on the settings page

{{end}}