Collect the username when data scouting data is submitted
This patch adds the username of the person that is submitting scouting
data.
I kind of amended the existing unit test to validate this feature by
injecting a fake username at the right places. It doesn't validate
actual HTTPS traffic, but it's good enough for now.
Signed-off-by: Philipp Schrader <philipp.schrader@gmail.com>
Change-Id: I483cf30fd046965b23916b129a074906b586b096
diff --git a/scouting/db/db.go b/scouting/db/db.go
index 0d9bec1..98cc788 100644
--- a/scouting/db/db.go
+++ b/scouting/db/db.go
@@ -25,6 +25,10 @@
ShotsMissed, UpperGoalShots, LowerGoalShots int32
ShotsMissedAuto, UpperGoalAuto, LowerGoalAuto, PlayedDefense int32
Climbing int32
+ // The username of the person who collected these statistics.
+ // "unknown" if submitted without logging in.
+ // Empty if the stats have not yet been collected.
+ CollectedBy string
}
type NotesData struct {
@@ -83,7 +87,8 @@
"UpperGoalAuto INTEGER, " +
"LowerGoalAuto INTEGER, " +
"PlayedDefense INTEGER, " +
- "Climbing INTEGER)")
+ "Climbing INTEGER, " +
+ "CollectedBy VARCHAR)")
if err != nil {
database.Close()
return nil, errors.New(fmt.Sprint("Failed to prepare stats table creation: ", err))
@@ -149,12 +154,12 @@
"TeamNumber, MatchNumber, " +
"ShotsMissed, UpperGoalShots, LowerGoalShots, " +
"ShotsMissedAuto, UpperGoalAuto, LowerGoalAuto, " +
- "PlayedDefense, Climbing) " +
+ "PlayedDefense, Climbing, CollectedBy) " +
"VALUES (" +
"$1, $2, " +
"$3, $4, $5, " +
"$6, $7, $8, " +
- "$9, $10) " +
+ "$9, $10, $11) " +
"RETURNING id")
if err != nil {
return errors.New(fmt.Sprint("Failed to prepare insertion into stats database: ", err))
@@ -163,7 +168,7 @@
var rowIds [6]int64
for i, TeamNumber := range []int32{m.R1, m.R2, m.R3, m.B1, m.B2, m.B3} {
- row := statement.QueryRow(TeamNumber, m.MatchNumber, 0, 0, 0, 0, 0, 0, 0, 0)
+ row := statement.QueryRow(TeamNumber, m.MatchNumber, 0, 0, 0, 0, 0, 0, 0, 0, "")
err = row.Scan(&rowIds[i])
if err != nil {
return errors.New(fmt.Sprint("Failed to insert stats: ", err))
@@ -197,8 +202,8 @@
"TeamNumber = $1, MatchNumber = $2, " +
"ShotsMissed = $3, UpperGoalShots = $4, LowerGoalShots = $5, " +
"ShotsMissedAuto = $6, UpperGoalAuto = $7, LowerGoalAuto = $8, " +
- "PlayedDefense = $9, Climbing = $10 " +
- "WHERE MatchNumber = $11 AND TeamNumber = $12")
+ "PlayedDefense = $9, Climbing = $10, CollectedBy = $11 " +
+ "WHERE MatchNumber = $12 AND TeamNumber = $13")
if err != nil {
return errors.New(fmt.Sprint("Failed to prepare stats update statement: ", err))
}
@@ -207,7 +212,7 @@
result, err := statement.Exec(s.TeamNumber, s.MatchNumber,
s.ShotsMissed, s.UpperGoalShots, s.LowerGoalShots,
s.ShotsMissedAuto, s.UpperGoalAuto, s.LowerGoalAuto,
- s.PlayedDefense, s.Climbing,
+ s.PlayedDefense, s.Climbing, s.CollectedBy,
s.MatchNumber, s.TeamNumber)
if err != nil {
return errors.New(fmt.Sprint("Failed to update stats database: ", err))
@@ -261,7 +266,7 @@
err = rows.Scan(&id, &team.TeamNumber, &team.MatchNumber,
&team.ShotsMissed, &team.UpperGoalShots, &team.LowerGoalShots,
&team.ShotsMissedAuto, &team.UpperGoalAuto, &team.LowerGoalAuto,
- &team.PlayedDefense, &team.Climbing)
+ &team.PlayedDefense, &team.Climbing, &team.CollectedBy)
if err != nil {
return nil, errors.New(fmt.Sprint("Failed to scan from stats: ", err))
}
@@ -308,7 +313,7 @@
err = rows.Scan(&id, &team.TeamNumber, &team.MatchNumber,
&team.ShotsMissed, &team.UpperGoalShots, &team.LowerGoalShots,
&team.ShotsMissedAuto, &team.UpperGoalAuto, &team.LowerGoalAuto,
- &team.PlayedDefense, &team.Climbing)
+ &team.PlayedDefense, &team.Climbing, &team.CollectedBy)
if err != nil {
return nil, errors.New(fmt.Sprint("Failed to scan from stats: ", err))
}
diff --git a/scouting/db/db_test.go b/scouting/db/db_test.go
index 16bc176..b06706d 100644
--- a/scouting/db/db_test.go
+++ b/scouting/db/db_test.go
@@ -92,36 +92,42 @@
ShotsMissed: 9, UpperGoalShots: 5, LowerGoalShots: 4,
ShotsMissedAuto: 3, UpperGoalAuto: 2, LowerGoalAuto: 1,
PlayedDefense: 2, Climbing: 3,
+ CollectedBy: "josh",
},
Stats{
TeamNumber: 1001, MatchNumber: 7,
ShotsMissed: 6, UpperGoalShots: 9, LowerGoalShots: 9,
ShotsMissedAuto: 0, UpperGoalAuto: 0, LowerGoalAuto: 0,
PlayedDefense: 0, Climbing: 0,
+ CollectedBy: "rupert",
},
Stats{
TeamNumber: 777, MatchNumber: 7,
ShotsMissed: 5, UpperGoalShots: 7, LowerGoalShots: 12,
ShotsMissedAuto: 0, UpperGoalAuto: 4, LowerGoalAuto: 0,
PlayedDefense: 0, Climbing: 0,
+ CollectedBy: "felix",
},
Stats{
TeamNumber: 1000, MatchNumber: 7,
ShotsMissed: 12, UpperGoalShots: 6, LowerGoalShots: 10,
ShotsMissedAuto: 0, UpperGoalAuto: 7, LowerGoalAuto: 0,
PlayedDefense: 0, Climbing: 0,
+ CollectedBy: "thea",
},
Stats{
TeamNumber: 4321, MatchNumber: 7,
ShotsMissed: 14, UpperGoalShots: 12, LowerGoalShots: 3,
ShotsMissedAuto: 0, UpperGoalAuto: 7, LowerGoalAuto: 0,
PlayedDefense: 0, Climbing: 0,
+ CollectedBy: "amy",
},
Stats{
TeamNumber: 1234, MatchNumber: 7,
ShotsMissed: 3, UpperGoalShots: 4, LowerGoalShots: 0,
ShotsMissedAuto: 0, UpperGoalAuto: 9, LowerGoalAuto: 0,
PlayedDefense: 0, Climbing: 0,
+ CollectedBy: "beth",
},
}
diff --git a/scouting/webserver/requests/debug/cli/cli_test.py b/scouting/webserver/requests/debug/cli/cli_test.py
index f4b82b4..b20703b 100644
--- a/scouting/webserver/requests/debug/cli/cli_test.py
+++ b/scouting/webserver/requests/debug/cli/cli_test.py
@@ -91,7 +91,8 @@
UpperGoalTele: (int32) 14,
LowerGoalTele: (int32) 15,
DefenseRating: (int32) 3,
- Climbing: (int32) 1
+ Climbing: (int32) 1,
+ CollectedBy: (string) (len=9) "debug_cli"
}"""), stdout)
def test_request_all_matches(self):
diff --git a/scouting/webserver/requests/debug/debug.go b/scouting/webserver/requests/debug/debug.go
index 81be3d1..49b6f79 100644
--- a/scouting/webserver/requests/debug/debug.go
+++ b/scouting/webserver/requests/debug/debug.go
@@ -2,6 +2,7 @@
import (
"bytes"
+ "encoding/base64"
"errors"
"fmt"
"io"
@@ -16,6 +17,9 @@
"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
)
+// The username to submit the various requests as.
+const DefaultUsername = "debug_cli"
+
// Use aliases to make the rest of the code more readable.
type SubmitDataScoutingResponseT = submit_data_scouting_response.SubmitDataScoutingResponseT
type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
@@ -66,7 +70,16 @@
// Performs a POST request with the specified payload. The bytes that the
// server responds with are returned.
func performPost(url string, requestBytes []byte) ([]byte, error) {
- resp, err := http.Post(url, "application/octet-stream", bytes.NewReader(requestBytes))
+ req, err := http.NewRequest("POST", url, bytes.NewReader(requestBytes))
+ if err != nil {
+ log.Printf("Failed to create a new POST request to %s: %v", url, err)
+ return nil, err
+ }
+ req.Header.Add("Authorization", "Basic "+
+ base64.StdEncoding.EncodeToString([]byte(DefaultUsername+":")))
+
+ client := &http.Client{}
+ resp, err := client.Do(req)
if err != nil {
log.Printf("Failed to send POST request to %s: %v", url, err)
return nil, err
diff --git a/scouting/webserver/requests/messages/request_data_scouting_response.fbs b/scouting/webserver/requests/messages/request_data_scouting_response.fbs
index e30ab42..5703a07 100644
--- a/scouting/webserver/requests/messages/request_data_scouting_response.fbs
+++ b/scouting/webserver/requests/messages/request_data_scouting_response.fbs
@@ -11,6 +11,7 @@
lower_goal_tele:int (id:7);
defense_rating:int (id:8);
climbing:int (id:9);
+ collected_by:string (id:10);
}
table RequestDataScoutingResponse {
diff --git a/scouting/webserver/requests/requests.go b/scouting/webserver/requests/requests.go
index 245891a..2076030 100644
--- a/scouting/webserver/requests/requests.go
+++ b/scouting/webserver/requests/requests.go
@@ -1,9 +1,11 @@
package requests
import (
+ "encoding/base64"
"errors"
"fmt"
"io"
+ "log"
"net/http"
"strconv"
"strings"
@@ -80,12 +82,44 @@
return result, success
}
+// Parses the authorization information that the browser inserts into the
+// headers. The authorization follows this format:
+//
+// req.Headers["Authorization"] = []string{"Basic <base64 encoded username:password>"}
+func parseUsername(req *http.Request) string {
+ auth, ok := req.Header["Authorization"]
+ if !ok {
+ return "unknown"
+ }
+
+ parts := strings.Split(auth[0], " ")
+ if !(len(parts) == 2 && parts[0] == "Basic") {
+ return "unknown"
+ }
+
+ info, err := base64.StdEncoding.DecodeString(parts[1])
+ if err != nil {
+ log.Println("ERROR: Failed to parse Basic authentication.")
+ return "unknown"
+ }
+
+ loginParts := strings.Split(string(info), ":")
+ if len(loginParts) != 2 {
+ return "unknown"
+ }
+ return loginParts[0]
+}
+
// Handles a SubmitDataScouting request.
type submitDataScoutingHandler struct {
db Database
}
func (handler submitDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ // Get the username of the person submitting the data.
+ username := parseUsername(req)
+ log.Println("Got data scouting data from", username)
+
requestBytes, err := io.ReadAll(req.Body)
if err != nil {
respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
@@ -108,6 +142,7 @@
LowerGoalShots: request.LowerGoalTele(),
PlayedDefense: request.DefenseRating(),
Climbing: request.Climbing(),
+ CollectedBy: username,
}
err = handler.db.AddToStats(stats)
@@ -152,7 +187,7 @@
matches, err := handler.db.ReturnMatches()
if err != nil {
- respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
+ respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
return
}
@@ -281,6 +316,7 @@
LowerGoalTele: stat.LowerGoalShots,
DefenseRating: stat.PlayedDefense,
Climbing: stat.Climbing,
+ CollectedBy: stat.CollectedBy,
})
}
diff --git a/scouting/webserver/requests/requests_test.go b/scouting/webserver/requests/requests_test.go
index 60bee0e..0183736 100644
--- a/scouting/webserver/requests/requests_test.go
+++ b/scouting/webserver/requests/requests_test.go
@@ -222,12 +222,14 @@
ShotsMissed: 1, UpperGoalShots: 2, LowerGoalShots: 3,
ShotsMissedAuto: 4, UpperGoalAuto: 5, LowerGoalAuto: 6,
PlayedDefense: 7, Climbing: 8,
+ CollectedBy: "john",
},
{
TeamNumber: 972, MatchNumber: 1,
ShotsMissed: 2, UpperGoalShots: 3, LowerGoalShots: 4,
ShotsMissedAuto: 5, UpperGoalAuto: 6, LowerGoalAuto: 7,
PlayedDefense: 8, Climbing: 9,
+ CollectedBy: "andrea",
},
},
}
@@ -250,17 +252,20 @@
// MissedShotsAuto, UpperGoalAuto, LowerGoalAuto,
// MissedShotsTele, UpperGoalTele, LowerGoalTele,
// DefenseRating, Climbing,
+ // CollectedBy,
{
971, 1,
4, 5, 6,
1, 2, 3,
7, 8,
+ "john",
},
{
972, 1,
5, 6, 7,
2, 3, 4,
8, 9,
+ "andrea",
},
},
}