Add TBA API for team info and awards

This commit is contained in:
Sam Baumgarten
2015-04-01 14:19:14 -07:00
parent 52c316014b
commit 1774197c13
9 changed files with 142 additions and 107 deletions

View File

@@ -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);

View File

@@ -0,0 +1,3 @@
-- +goose Up
ALTER TABLE event_settings ADD COLUMN tbadownloadenabled BOOLEAN;
ALTER TABLE event_settings ADD COLUMN tbaawardsdownloadenabled BOOLEAN;

View File

@@ -6,29 +6,28 @@
package main package main
type EventSettings struct { type EventSettings struct {
Id int Id int
Name string Name string
Code string Code string
DisplayBackgroundColor string DisplayBackgroundColor string
NumElimAlliances int NumElimAlliances int
SelectionRound2Order string SelectionRound2Order string
SelectionRound3Order string SelectionRound3Order string
FMSAPIDownloadEnabled bool TBADownloadEnabled bool
FMSAPIUsername string TBAAwardsDownloadEnabled bool
FMSAPIAuthKey string AllianceDisplayHotGoals bool
AllianceDisplayHotGoals bool RedGoalLightsAddress string
RedGoalLightsAddress string BlueGoalLightsAddress string
BlueGoalLightsAddress string TbaPublishingEnabled bool
TbaPublishingEnabled bool TbaEventCode string
TbaEventCode string TbaSecretId string
TbaSecretId string TbaSecret string
TbaSecret string NetworkSecurityEnabled bool
NetworkSecurityEnabled bool ApAddress string
ApAddress string ApUsername string
ApUsername string ApPassword string
ApPassword string SwitchAddress string
SwitchAddress string SwitchPassword string
SwitchPassword string
} }
const eventSettingsId = 0 const eventSettingsId = 0
@@ -44,7 +43,8 @@ func (database *Database) GetEventSettings() (*EventSettings, error) {
eventSettings.NumElimAlliances = 8 eventSettings.NumElimAlliances = 8
eventSettings.SelectionRound2Order = "L" eventSettings.SelectionRound2Order = "L"
eventSettings.SelectionRound3Order = "" eventSettings.SelectionRound3Order = ""
eventSettings.FMSAPIDownloadEnabled = false eventSettings.TBADownloadEnabled = true
eventSettings.TBAAwardsDownloadEnabled = true
err = database.eventSettingsMap.Insert(eventSettings) err = database.eventSettingsMap.Insert(eventSettings)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@@ -19,7 +19,7 @@ func TestEventSettingsReadWrite(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, EventSettings{Id: 0, Name: "Untitled Event", Code: "UE", DisplayBackgroundColor: "#00ff00", assert.Equal(t, EventSettings{Id: 0, Name: "Untitled Event", Code: "UE", DisplayBackgroundColor: "#00ff00",
NumElimAlliances: 8, SelectionRound2Order: "L", SelectionRound3Order: "", NumElimAlliances: 8, SelectionRound2Order: "L", SelectionRound3Order: "",
FMSAPIDownloadEnabled: false}, *eventSettings) TBADownloadEnabled: true, TBAAwardsDownloadEnabled: true}, *eventSettings)
eventSettings.Name = "Chezy Champs" eventSettings.Name = "Chezy Champs"
eventSettings.Code = "cc" eventSettings.Code = "cc"

View File

@@ -42,9 +42,8 @@ func SettingsPostHandler(w http.ResponseWriter, r *http.Request) {
eventSettings.NumElimAlliances = numAlliances eventSettings.NumElimAlliances = numAlliances
eventSettings.SelectionRound2Order = r.PostFormValue("selectionRound2Order") eventSettings.SelectionRound2Order = r.PostFormValue("selectionRound2Order")
eventSettings.SelectionRound3Order = r.PostFormValue("selectionRound3Order") eventSettings.SelectionRound3Order = r.PostFormValue("selectionRound3Order")
eventSettings.FMSAPIDownloadEnabled = r.PostFormValue("FMSAPIDownloadEnabled") == "on" eventSettings.TBADownloadEnabled = r.PostFormValue("TBADownloadEnabled") == "on"
eventSettings.FMSAPIUsername = r.PostFormValue("FMSAPIUsername") eventSettings.TBAAwardsDownloadEnabled = r.PostFormValue("TBAAwardsDownloadEnabled") == "on"
eventSettings.FMSAPIAuthKey = r.PostFormValue("FMSAPIAuthKey")
eventSettings.AllianceDisplayHotGoals = r.PostFormValue("allianceDisplayHotGoals") == "on" eventSettings.AllianceDisplayHotGoals = r.PostFormValue("allianceDisplayHotGoals") == "on"
eventSettings.RedGoalLightsAddress = r.PostFormValue("redGoalLightsAddress") eventSettings.RedGoalLightsAddress = r.PostFormValue("redGoalLightsAddress")
eventSettings.BlueGoalLightsAddress = r.PostFormValue("blueGoalLightsAddress") eventSettings.BlueGoalLightsAddress = r.PostFormValue("blueGoalLightsAddress")

View File

@@ -6,12 +6,12 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"time"
"bytes"
"github.com/dchest/uniuri" "github.com/dchest/uniuri"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"html/template" "html/template"
"io/ioutil"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@@ -19,7 +19,7 @@ import (
const wpaKeyLength = 8 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. // Shows the team list.
func TeamsGetHandler(w http.ResponseWriter, r *http.Request) { func TeamsGetHandler(w http.ResponseWriter, r *http.Request) {
@@ -233,71 +233,39 @@ func canModifyTeamList() bool {
func getOfficialTeamInfo(teamId int) (*Team, error) { func getOfficialTeamInfo(teamId int) (*Team, error) {
// Create the team variable that stores the result // Create the team variable that stores the result
var team Team 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 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 != "" { if eventSettings.TBADownloadEnabled {
// Make an HTTP GET request with basic auth to get the info var tbaTeam *TbaTeam = getTeamFromTba(teamId)
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
}
// Get the response and handle errors // Check if the result is valid. If a team is not found, just return a basic team
defer resp.Body.Close() if tbaTeam == nil {
body, err := ioutil.ReadAll(resp.Body) team = Team{Id: teamId}
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}
return &team, nil 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 recentAwards []TbaAward;
var name string if eventSettings.TBAAwardsDownloadEnabled {
var nickname string recentAwards = getTeamAwardsFromTba(teamId)
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 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 // Use those variables to make a team object
team = Team{Id: teamId, Name: name, Nickname: nickname, team = Team{Id: teamId, Name: tbaTeam.Name, Nickname: tbaTeam.Nickname,
City: city, StateProv: stateProv, City: tbaTeam.Locality, StateProv: tbaTeam.Reigon,
Country: country, RookieYear: rookieYear, Country: tbaTeam.Country, RookieYear: tbaTeam.RookieYear, Accomplishments: accomplishmentsBuffer.String()}
RobotName: robotName} } else {
} else { // If team grab is disabled, just use the team number
// If team grab is disabled, just use the team number team = Team{Id: teamId}
team = Team{Id: teamId} }
}
// Return the team object // Return the team object
return &team, nil return &team, nil

75
tba.go
View File

@@ -17,6 +17,8 @@ import (
var tbaBaseUrl = "http://www.thebluealliance.com" var tbaBaseUrl = "http://www.thebluealliance.com"
// MODELS
type TbaMatch struct { type TbaMatch struct {
CompLevel string `json:"comp_level"` CompLevel string `json:"comp_level"`
SetNumber int `json:"set_number"` SetNumber int `json:"set_number"`
@@ -49,6 +51,65 @@ type TbaRanking struct {
GoalFoul int `json:"G&F"` 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. // Uploads the event team list to The Blue Alliance.
func PublishTeams() error { func PublishTeams() error {
teams, err := db.GetAllTeams() teams, err := db.GetAllTeams()
@@ -213,6 +274,8 @@ func getTbaTeam(team int) string {
return fmt.Sprintf("frc%d", team) return fmt.Sprintf("frc%d", team)
} }
// HELPERS
// Signs the request and sends it to the TBA API. // Signs the request and sends it to the TBA API.
func postTbaRequest(resource string, body []byte) (*http.Response, error) { func postTbaRequest(resource string, body []byte) (*http.Response, error) {
path := fmt.Sprintf("/api/trusted/v1/event/%s/%s/update", eventSettings.TbaEventCode, resource) 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) request.Header.Add("X-TBA-Auth-Sig", signature)
return client.Do(request) 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)
}

View File

@@ -93,23 +93,17 @@
</div> </div>
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>FRC Events API</legend> <legend>Automatic Team Info Download</legend>
<div class="form-group"> <div class="form-group">
<label class="col-lg-7 control-label">Enable FRC Events API Team Info Download</label> <label class="col-lg-9 control-label">Enable Automatic Team Info Download (From TBA)</label>
<div class="col-lg-1 checkbox"> <div class="col-lg-1 checkbox">
<input type="checkbox" name="FMSAPIDownloadEnabled"{{if .FMSAPIDownloadEnabled}} checked{{end}}> <input type="checkbox" name="TBADownloadEnabled"{{if .TBADownloadEnabled}} checked{{end}}>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="col-lg-5 control-label">FRC Events API Username</label> <label class="col-lg-9 control-label">Enable Automatic Team Accomplishments Download (From TBA)</label>
<div class="col-lg-7"> <div class="col-lg-1 checkbox">
<input type="text" class="form-control" name="FMSAPIUsername" value="{{.FMSAPIUsername}}"> <input type="checkbox" name="TBADownloadEnabled"{{if .TBAAwardsDownloadEnabled}} checked{{end}}>
</div>
</div>
<div class="form-group">
<label class="col-lg-5 control-label">FRC Events API Authorization Key</label>
<div class="col-lg-7">
<input type="text" class="form-control" name="FMSAPIAuthKey" value="{{.FMSAPIAuthKey}}">
</div> </div>
</div> </div>
</fieldset> </fieldset>

View File

@@ -18,7 +18,7 @@
<form class="form-horizontal" action="/setup/teams" method="POST"> <form class="form-horizontal" action="/setup/teams" method="POST">
<fieldset> <fieldset>
<legend>Import Teams</legend> <legend>Import Teams</legend>
{{if not .EventSettings.FMSAPIDownloadEnabled}}<p>To automatically download data about teams, enter your FRC Events API key on the event settings page</p>{{end}} {{if not .EventSettings.TBADownloadEnabled}}<p>To automatically download data about teams, enable TBA Team Info Download on the settings page</p>{{end}}
<div class="form-group"> <div class="form-group">
<textarea class="form-control" rows="10" name="teamNumbers" <textarea class="form-control" rows="10" name="teamNumbers"
placeholder="One team number per line"></textarea> placeholder="One team number per line"></textarea>