blob: e33e82d9c433f907186f80ce1242d41ac7f4b89f [file] [log] [blame]
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08001package requests
2
3import (
Philipp Schraderfae8a7e2022-03-13 22:51:54 -07004 "encoding/base64"
Philipp Schraderd3fac192022-03-02 20:35:46 -08005 "errors"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08006 "fmt"
7 "io"
Philipp Schraderfae8a7e2022-03-13 22:51:54 -07008 "log"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08009 "net/http"
Philipp Schraderd3fac192022-03-02 20:35:46 -080010 "strconv"
11 "strings"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080012
Philipp Schrader8747f1b2022-02-23 23:56:22 -080013 "github.com/frc971/971-Robot-Code/scouting/db"
Philipp Schraderd3fac192022-03-02 20:35:46 -080014 "github.com/frc971/971-Robot-Code/scouting/scraping"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080015 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
Philipp Schraderd3fac192022-03-02 20:35:46 -080016 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/refresh_match_list"
17 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/refresh_match_list_response"
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080018 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches"
19 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches_response"
Philipp Schraderacf96232022-03-01 22:03:30 -080020 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_data_scouting"
21 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_data_scouting_response"
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080022 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team"
23 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070024 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team"
25 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070026 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule"
27 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080028 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting"
Philipp Schrader30005e42022-03-06 13:53:58 -080029 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070030 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes"
31 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070032 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule"
33 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080034 "github.com/frc971/971-Robot-Code/scouting/webserver/server"
35 flatbuffers "github.com/google/flatbuffers/go"
36)
37
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080038type SubmitDataScouting = submit_data_scouting.SubmitDataScouting
Philipp Schrader30005e42022-03-06 13:53:58 -080039type SubmitDataScoutingResponseT = submit_data_scouting_response.SubmitDataScoutingResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080040type RequestAllMatches = request_all_matches.RequestAllMatches
41type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080042type RequestMatchesForTeam = request_matches_for_team.RequestMatchesForTeam
43type RequestMatchesForTeamResponseT = request_matches_for_team_response.RequestMatchesForTeamResponseT
Philipp Schraderacf96232022-03-01 22:03:30 -080044type RequestDataScouting = request_data_scouting.RequestDataScouting
45type RequestDataScoutingResponseT = request_data_scouting_response.RequestDataScoutingResponseT
Philipp Schraderd3fac192022-03-02 20:35:46 -080046type RefreshMatchList = refresh_match_list.RefreshMatchList
47type RefreshMatchListResponseT = refresh_match_list_response.RefreshMatchListResponseT
Alex Perry81f96ba2022-03-13 18:26:19 -070048type SubmitNotes = submit_notes.SubmitNotes
49type SubmitNotesResponseT = submit_notes_response.SubmitNotesResponseT
50type RequestNotesForTeam = request_notes_for_team.RequestNotesForTeam
51type RequestNotesForTeamResponseT = request_notes_for_team_response.RequestNotesForTeamResponseT
Milo Lin1d59f0c2022-06-22 20:30:58 -070052type RequestShiftSchedule = request_shift_schedule.RequestShiftSchedule
53type RequestShiftScheduleResponseT = request_shift_schedule_response.RequestShiftScheduleResponseT
54type SubmitShiftSchedule = submit_shift_schedule.SubmitShiftSchedule
55type SubmitShiftScheduleResponseT = submit_shift_schedule_response.SubmitShiftScheduleResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080056
Philipp Schrader8747f1b2022-02-23 23:56:22 -080057// The interface we expect the database abstraction to conform to.
58// We use an interface here because it makes unit testing easier.
59type Database interface {
60 AddToMatch(db.Match) error
Milo Lin1d59f0c2022-06-22 20:30:58 -070061 AddToShift(db.Shift) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080062 AddToStats(db.Stats) error
63 ReturnMatches() ([]db.Match, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070064 ReturnAllShifts() ([]db.Shift, error)
Philipp Schrader8747f1b2022-02-23 23:56:22 -080065 ReturnStats() ([]db.Stats, error)
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080066 QueryMatches(int32) ([]db.Match, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070067 QueryAllShifts(int) ([]db.Shift, error)
Philipp Schrader8747f1b2022-02-23 23:56:22 -080068 QueryStats(int) ([]db.Stats, error)
Philipp Schradereecb8962022-06-01 21:02:42 -070069 QueryNotes(int32) ([]string, error)
Filip Kujawaf947cb42022-11-21 10:00:30 -080070 AddNotes(db.NotesData) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080071}
72
Philipp Schraderd3fac192022-03-02 20:35:46 -080073type ScrapeMatchList func(int32, string) ([]scraping.Match, error)
74
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080075// Handles unknown requests. Just returns a 404.
76func unknown(w http.ResponseWriter, req *http.Request) {
77 w.WriteHeader(http.StatusNotFound)
78}
79
80func respondWithError(w http.ResponseWriter, statusCode int, errorMessage string) {
81 builder := flatbuffers.NewBuilder(1024)
82 builder.Finish((&error_response.ErrorResponseT{
83 ErrorMessage: errorMessage,
84 }).Pack(builder))
85 w.WriteHeader(statusCode)
86 w.Write(builder.FinishedBytes())
87}
88
89func respondNotImplemented(w http.ResponseWriter) {
90 respondWithError(w, http.StatusNotImplemented, "")
91}
92
Philipp Schraderb7e75932022-03-26 16:18:34 -070093func parseRequest[T interface{}](w http.ResponseWriter, buf []byte, requestName string, parser func([]byte, flatbuffers.UOffsetT) *T) (*T, bool) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080094 success := true
95 defer func() {
96 if r := recover(); r != nil {
Philipp Schraderb7e75932022-03-26 16:18:34 -070097 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse %s: %v", requestName, r))
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080098 success = false
99 }
100 }()
Philipp Schraderb7e75932022-03-26 16:18:34 -0700101 result := parser(buf, 0)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800102 return result, success
103}
104
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700105// Parses the authorization information that the browser inserts into the
106// headers. The authorization follows this format:
107//
108// req.Headers["Authorization"] = []string{"Basic <base64 encoded username:password>"}
109func parseUsername(req *http.Request) string {
110 auth, ok := req.Header["Authorization"]
111 if !ok {
112 return "unknown"
113 }
114
115 parts := strings.Split(auth[0], " ")
116 if !(len(parts) == 2 && parts[0] == "Basic") {
117 return "unknown"
118 }
119
120 info, err := base64.StdEncoding.DecodeString(parts[1])
121 if err != nil {
122 log.Println("ERROR: Failed to parse Basic authentication.")
123 return "unknown"
124 }
125
126 loginParts := strings.Split(string(info), ":")
127 if len(loginParts) != 2 {
128 return "unknown"
129 }
130 return loginParts[0]
131}
132
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800133// Handles a SubmitDataScouting request.
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800134type submitDataScoutingHandler struct {
135 db Database
136}
137
138func (handler submitDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700139 // Get the username of the person submitting the data.
140 username := parseUsername(req)
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700141
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800142 requestBytes, err := io.ReadAll(req.Body)
143 if err != nil {
144 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
145 return
146 }
147
Philipp Schraderb7e75932022-03-26 16:18:34 -0700148 request, success := parseRequest[SubmitDataScouting](w, requestBytes, "SubmitDataScouting", submit_data_scouting.GetRootAsSubmitDataScouting)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800149 if !success {
150 return
151 }
152
Philipp Schraderd7b6eba2022-03-18 22:21:25 -0700153 log.Println("Got data scouting data for match", request.Match(), "team", request.Team(), "from", username)
154
Philipp Schrader30005e42022-03-06 13:53:58 -0800155 stats := db.Stats{
Philipp Schraderfee07e12022-03-17 22:19:47 -0700156 TeamNumber: request.Team(),
157 MatchNumber: request.Match(),
Philipp Schrader30b4a682022-04-16 14:36:17 -0700158 SetNumber: request.SetNumber(),
Philipp Schrader4535b7e2022-04-08 20:27:00 -0700159 CompLevel: string(request.CompLevel()),
Philipp Schraderfee07e12022-03-17 22:19:47 -0700160 StartingQuadrant: request.StartingQuadrant(),
161 AutoBallPickedUp: [5]bool{
162 request.AutoBall1(), request.AutoBall2(), request.AutoBall3(),
163 request.AutoBall4(), request.AutoBall5(),
164 },
Philipp Schraderfa45d742022-03-18 19:29:05 -0700165 ShotsMissedAuto: request.MissedShotsAuto(),
166 UpperGoalAuto: request.UpperGoalAuto(),
167 LowerGoalAuto: request.LowerGoalAuto(),
168 ShotsMissed: request.MissedShotsTele(),
169 UpperGoalShots: request.UpperGoalTele(),
170 LowerGoalShots: request.LowerGoalTele(),
171 PlayedDefense: request.DefenseRating(),
172 DefenseReceivedScore: request.DefenseReceivedRating(),
173 Climbing: int32(request.ClimbLevel()),
174 CollectedBy: username,
175 Comment: string(request.Comment()),
Philipp Schrader30005e42022-03-06 13:53:58 -0800176 }
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800177
Philipp Schraderfee07e12022-03-17 22:19:47 -0700178 // Do some error checking.
179 if stats.StartingQuadrant < 1 || stats.StartingQuadrant > 4 {
180 respondWithError(w, http.StatusBadRequest, fmt.Sprint(
181 "Invalid starting_quadrant field value of ", stats.StartingQuadrant))
182 return
183 }
184
Philipp Schrader30005e42022-03-06 13:53:58 -0800185 err = handler.db.AddToStats(stats)
186 if err != nil {
187 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit datascouting data: ", err))
Philipp Schraderfee07e12022-03-17 22:19:47 -0700188 return
Philipp Schrader30005e42022-03-06 13:53:58 -0800189 }
190
191 builder := flatbuffers.NewBuilder(50 * 1024)
192 builder.Finish((&SubmitDataScoutingResponseT{}).Pack(builder))
193 w.Write(builder.FinishedBytes())
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800194}
195
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800196// Handles a RequestAllMaches request.
197type requestAllMatchesHandler struct {
198 db Database
199}
200
201func (handler requestAllMatchesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
202 requestBytes, err := io.ReadAll(req.Body)
203 if err != nil {
204 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
205 return
206 }
207
Philipp Schraderb7e75932022-03-26 16:18:34 -0700208 _, success := parseRequest(w, requestBytes, "RequestAllMatches", request_all_matches.GetRootAsRequestAllMatches)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800209 if !success {
210 return
211 }
212
213 matches, err := handler.db.ReturnMatches()
214 if err != nil {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700215 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800216 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800217 }
218
219 var response RequestAllMatchesResponseT
220 for _, match := range matches {
221 response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
222 MatchNumber: match.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700223 SetNumber: match.SetNumber,
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800224 CompLevel: match.CompLevel,
225 R1: match.R1,
226 R2: match.R2,
227 R3: match.R3,
228 B1: match.B1,
229 B2: match.B2,
230 B3: match.B3,
231 })
232 }
233
234 builder := flatbuffers.NewBuilder(50 * 1024)
235 builder.Finish((&response).Pack(builder))
236 w.Write(builder.FinishedBytes())
237}
238
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800239// Handles a RequestMatchesForTeam request.
240type requestMatchesForTeamHandler struct {
241 db Database
242}
243
244func (handler requestMatchesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
245 requestBytes, err := io.ReadAll(req.Body)
246 if err != nil {
247 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
248 return
249 }
250
Philipp Schraderb7e75932022-03-26 16:18:34 -0700251 request, success := parseRequest(w, requestBytes, "RequestMatchesForTeam", request_matches_for_team.GetRootAsRequestMatchesForTeam)
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800252 if !success {
253 return
254 }
255
256 matches, err := handler.db.QueryMatches(request.Team())
257 if err != nil {
258 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800259 return
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800260 }
261
262 var response RequestAllMatchesResponseT
263 for _, match := range matches {
264 response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
265 MatchNumber: match.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700266 SetNumber: match.SetNumber,
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800267 CompLevel: match.CompLevel,
268 R1: match.R1,
269 R2: match.R2,
270 R3: match.R3,
271 B1: match.B1,
272 B2: match.B2,
273 B3: match.B3,
274 })
275 }
276
277 builder := flatbuffers.NewBuilder(50 * 1024)
278 builder.Finish((&response).Pack(builder))
279 w.Write(builder.FinishedBytes())
280}
281
Philipp Schraderacf96232022-03-01 22:03:30 -0800282// Handles a RequestDataScouting request.
283type requestDataScoutingHandler struct {
284 db Database
285}
286
287func (handler requestDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
288 requestBytes, err := io.ReadAll(req.Body)
289 if err != nil {
290 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
291 return
292 }
293
Philipp Schraderb7e75932022-03-26 16:18:34 -0700294 _, success := parseRequest(w, requestBytes, "RequestDataScouting", request_data_scouting.GetRootAsRequestDataScouting)
Philipp Schraderacf96232022-03-01 22:03:30 -0800295 if !success {
296 return
297 }
298
299 stats, err := handler.db.ReturnStats()
300 if err != nil {
301 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800302 return
Philipp Schraderacf96232022-03-01 22:03:30 -0800303 }
304
305 var response RequestDataScoutingResponseT
306 for _, stat := range stats {
307 response.StatsList = append(response.StatsList, &request_data_scouting_response.StatsT{
Philipp Schraderfa45d742022-03-18 19:29:05 -0700308 Team: stat.TeamNumber,
309 Match: stat.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700310 SetNumber: stat.SetNumber,
Philipp Schrader4535b7e2022-04-08 20:27:00 -0700311 CompLevel: stat.CompLevel,
Philipp Schraderfa45d742022-03-18 19:29:05 -0700312 StartingQuadrant: stat.StartingQuadrant,
313 AutoBall1: stat.AutoBallPickedUp[0],
314 AutoBall2: stat.AutoBallPickedUp[1],
315 AutoBall3: stat.AutoBallPickedUp[2],
316 AutoBall4: stat.AutoBallPickedUp[3],
317 AutoBall5: stat.AutoBallPickedUp[4],
318 MissedShotsAuto: stat.ShotsMissedAuto,
319 UpperGoalAuto: stat.UpperGoalAuto,
320 LowerGoalAuto: stat.LowerGoalAuto,
321 MissedShotsTele: stat.ShotsMissed,
322 UpperGoalTele: stat.UpperGoalShots,
323 LowerGoalTele: stat.LowerGoalShots,
324 DefenseRating: stat.PlayedDefense,
325 DefenseReceivedRating: stat.DefenseReceivedScore,
326 ClimbLevel: request_data_scouting_response.ClimbLevel(stat.Climbing),
327 CollectedBy: stat.CollectedBy,
328 Comment: stat.Comment,
Philipp Schraderacf96232022-03-01 22:03:30 -0800329 })
330 }
331
332 builder := flatbuffers.NewBuilder(50 * 1024)
333 builder.Finish((&response).Pack(builder))
334 w.Write(builder.FinishedBytes())
335}
336
Philipp Schraderd3fac192022-03-02 20:35:46 -0800337func parseTeamKey(teamKey string) (int, error) {
338 // TBA prefixes teams with "frc". Not sure why. Get rid of that.
339 teamKey = strings.TrimPrefix(teamKey, "frc")
Philipp Schrader84fe7782022-11-12 08:10:36 -0800340 magnitude := 0
341 if strings.HasSuffix(teamKey, "A") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800342 magnitude = 0
Philipp Schrader84fe7782022-11-12 08:10:36 -0800343 teamKey = strings.TrimSuffix(teamKey, "A")
344 } else if strings.HasSuffix(teamKey, "B") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800345 magnitude = 9
Philipp Schrader84fe7782022-11-12 08:10:36 -0800346 teamKey = strings.TrimSuffix(teamKey, "B")
347 } else if strings.HasSuffix(teamKey, "C") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800348 magnitude = 8
Philipp Schrader84fe7782022-11-12 08:10:36 -0800349 teamKey = strings.TrimSuffix(teamKey, "C")
350 } else if strings.HasSuffix(teamKey, "D") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800351 magnitude = 7
Philipp Schrader84fe7782022-11-12 08:10:36 -0800352 teamKey = strings.TrimSuffix(teamKey, "D")
353 } else if strings.HasSuffix(teamKey, "E") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800354 magnitude = 6
Philipp Schrader84fe7782022-11-12 08:10:36 -0800355 teamKey = strings.TrimSuffix(teamKey, "E")
356 } else if strings.HasSuffix(teamKey, "F") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800357 magnitude = 5
Philipp Schrader84fe7782022-11-12 08:10:36 -0800358 teamKey = strings.TrimSuffix(teamKey, "F")
359 }
Filip Kujawa4de6feb2022-11-12 08:41:49 -0800360
361 if magnitude != 0 {
362 teamKey = strconv.Itoa(magnitude) + teamKey
Philipp Schrader84fe7782022-11-12 08:10:36 -0800363 }
Filip Kujawa4de6feb2022-11-12 08:41:49 -0800364
365 result, err := strconv.Atoi(teamKey)
Philipp Schrader84fe7782022-11-12 08:10:36 -0800366 return result, err
Philipp Schraderd3fac192022-03-02 20:35:46 -0800367}
368
369// Parses the alliance data from the specified match and returns the three red
370// teams and the three blue teams.
371func parseTeamKeys(match *scraping.Match) ([3]int32, [3]int32, error) {
372 redKeys := match.Alliances.Red.TeamKeys
373 blueKeys := match.Alliances.Blue.TeamKeys
374
375 if len(redKeys) != 3 || len(blueKeys) != 3 {
376 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
377 "Found %d red teams and %d blue teams.", len(redKeys), len(blueKeys)))
378 }
379
380 var red [3]int32
381 for i, key := range redKeys {
382 team, err := parseTeamKey(key)
383 if err != nil {
384 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
385 "Failed to parse red %d team '%s' as integer: %v", i+1, key, err))
386 }
387 red[i] = int32(team)
388 }
389 var blue [3]int32
390 for i, key := range blueKeys {
391 team, err := parseTeamKey(key)
392 if err != nil {
393 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
394 "Failed to parse blue %d team '%s' as integer: %v", i+1, key, err))
395 }
396 blue[i] = int32(team)
397 }
398 return red, blue, nil
399}
400
401type refreshMatchListHandler struct {
402 db Database
403 scrape ScrapeMatchList
404}
405
406func (handler refreshMatchListHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
407 requestBytes, err := io.ReadAll(req.Body)
408 if err != nil {
409 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
410 return
411 }
412
Philipp Schraderb7e75932022-03-26 16:18:34 -0700413 request, success := parseRequest(w, requestBytes, "RefreshMatchList", refresh_match_list.GetRootAsRefreshMatchList)
Philipp Schraderd3fac192022-03-02 20:35:46 -0800414 if !success {
415 return
416 }
417
418 matches, err := handler.scrape(request.Year(), string(request.EventCode()))
419 if err != nil {
420 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to scrape match list: ", err))
421 return
422 }
423
424 for _, match := range matches {
425 // Make sure the data is valid.
426 red, blue, err := parseTeamKeys(&match)
427 if err != nil {
428 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
429 "TheBlueAlliance data for match %d is malformed: %v", match.MatchNumber, err))
430 return
431 }
432 // Add the match to the database.
Philipp Schrader7365d322022-03-06 16:40:08 -0800433 err = handler.db.AddToMatch(db.Match{
Philipp Schraderd3fac192022-03-02 20:35:46 -0800434 MatchNumber: int32(match.MatchNumber),
Philipp Schrader30b4a682022-04-16 14:36:17 -0700435 SetNumber: int32(match.SetNumber),
Philipp Schrader45befdd2022-04-08 19:12:44 -0700436 CompLevel: match.CompLevel,
437 R1: red[0],
438 R2: red[1],
439 R3: red[2],
440 B1: blue[0],
441 B2: blue[1],
442 B3: blue[2],
Philipp Schraderd3fac192022-03-02 20:35:46 -0800443 })
Philipp Schrader7365d322022-03-06 16:40:08 -0800444 if err != nil {
445 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
446 "Failed to add match %d to the database: %v", match.MatchNumber, err))
447 return
448 }
Philipp Schraderd3fac192022-03-02 20:35:46 -0800449 }
450
451 var response RefreshMatchListResponseT
452 builder := flatbuffers.NewBuilder(1024)
453 builder.Finish((&response).Pack(builder))
454 w.Write(builder.FinishedBytes())
455}
456
Alex Perry81f96ba2022-03-13 18:26:19 -0700457type submitNoteScoutingHandler struct {
458 db Database
459}
460
461func (handler submitNoteScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
462 requestBytes, err := io.ReadAll(req.Body)
463 if err != nil {
464 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
465 return
466 }
467
Philipp Schraderb7e75932022-03-26 16:18:34 -0700468 request, success := parseRequest(w, requestBytes, "SubmitNotes", submit_notes.GetRootAsSubmitNotes)
Alex Perry81f96ba2022-03-13 18:26:19 -0700469 if !success {
470 return
471 }
472
Filip Kujawaf947cb42022-11-21 10:00:30 -0800473 err = handler.db.AddNotes(db.NotesData{
474 TeamNumber: request.Team(),
475 Notes: string(request.Notes()),
476 GoodDriving: bool(request.GoodDriving()),
477 BadDriving: bool(request.BadDriving()),
478 SketchyClimb: bool(request.SketchyClimb()),
479 SolidClimb: bool(request.SolidClimb()),
480 GoodDefense: bool(request.GoodDefense()),
481 BadDefense: bool(request.BadDefense()),
482 })
Alex Perry81f96ba2022-03-13 18:26:19 -0700483 if err != nil {
484 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert notes: %v", err))
485 return
486 }
487
488 var response SubmitNotesResponseT
489 builder := flatbuffers.NewBuilder(10)
490 builder.Finish((&response).Pack(builder))
491 w.Write(builder.FinishedBytes())
492}
493
Alex Perry81f96ba2022-03-13 18:26:19 -0700494type requestNotesForTeamHandler struct {
495 db Database
496}
497
498func (handler requestNotesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
499 requestBytes, err := io.ReadAll(req.Body)
500 if err != nil {
501 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
502 return
503 }
504
Philipp Schraderb7e75932022-03-26 16:18:34 -0700505 request, success := parseRequest(w, requestBytes, "RequestNotesForTeam", request_notes_for_team.GetRootAsRequestNotesForTeam)
Alex Perry81f96ba2022-03-13 18:26:19 -0700506 if !success {
507 return
508 }
509
Philipp Schradereecb8962022-06-01 21:02:42 -0700510 notes, err := handler.db.QueryNotes(request.Team())
Alex Perry81f96ba2022-03-13 18:26:19 -0700511 if err != nil {
512 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query notes: %v", err))
513 return
514 }
515
516 var response RequestNotesForTeamResponseT
Philipp Schradereecb8962022-06-01 21:02:42 -0700517 for _, data := range notes {
Alex Perry81f96ba2022-03-13 18:26:19 -0700518 response.Notes = append(response.Notes, &request_notes_for_team_response.NoteT{data})
519 }
520
521 builder := flatbuffers.NewBuilder(1024)
522 builder.Finish((&response).Pack(builder))
523 w.Write(builder.FinishedBytes())
524}
525
Milo Lin1d59f0c2022-06-22 20:30:58 -0700526type requestShiftScheduleHandler struct {
527 db Database
528}
529
530func (handler requestShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
531 requestBytes, err := io.ReadAll(req.Body)
532 if err != nil {
533 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
534 return
535 }
536
537 _, success := parseRequest(w, requestBytes, "RequestShiftSchedule", request_shift_schedule.GetRootAsRequestShiftSchedule)
538 if !success {
539 return
540 }
541
542 shiftData, err := handler.db.ReturnAllShifts()
543 if err != nil {
544 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query shift schedule: %v", err))
545 return
546 }
547
548 var response RequestShiftScheduleResponseT
549 for _, shifts := range shiftData {
550 response.ShiftSchedule = append(response.ShiftSchedule, &request_shift_schedule_response.MatchAssignmentT{
551 MatchNumber: shifts.MatchNumber,
552 R1scouter: shifts.R1scouter,
553 R2scouter: shifts.R2scouter,
554 R3scouter: shifts.R3scouter,
555 B1scouter: shifts.B1scouter,
556 B2scouter: shifts.B2scouter,
557 B3scouter: shifts.B3scouter,
558 })
559 }
560
561 builder := flatbuffers.NewBuilder(1024)
562 builder.Finish((&response).Pack(builder))
563 w.Write(builder.FinishedBytes())
564}
565
566type submitShiftScheduleHandler struct {
567 db Database
568}
569
570func (handler submitShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
571 // Get the username of the person submitting the data.
572 username := parseUsername(req)
573
574 requestBytes, err := io.ReadAll(req.Body)
575 if err != nil {
576 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
577 return
578 }
579
580 request, success := parseRequest[SubmitShiftSchedule](w, requestBytes, "SubmitShiftSchedule", submit_shift_schedule.GetRootAsSubmitShiftSchedule)
581 if !success {
582 return
583 }
584
585 log.Println("Got shift schedule from", username)
586 shift_schedule_length := request.ShiftScheduleLength()
587 for i := 0; i < shift_schedule_length; i++ {
588 var match_assignment submit_shift_schedule.MatchAssignment
589 request.ShiftSchedule(&match_assignment, i)
590 current_shift := db.Shift{
591 MatchNumber: match_assignment.MatchNumber(),
592 R1scouter: string(match_assignment.R1scouter()),
593 R2scouter: string(match_assignment.R2scouter()),
594 R3scouter: string(match_assignment.R3scouter()),
595 B1scouter: string(match_assignment.B1scouter()),
596 B2scouter: string(match_assignment.B2scouter()),
597 B3scouter: string(match_assignment.B3scouter()),
598 }
599 err = handler.db.AddToShift(current_shift)
600 if err != nil {
601 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit shift schedule: ", err))
602 return
603 }
604 }
605
606 builder := flatbuffers.NewBuilder(50 * 1024)
607 builder.Finish((&SubmitShiftScheduleResponseT{}).Pack(builder))
608 w.Write(builder.FinishedBytes())
609}
610
Philipp Schraderd3fac192022-03-02 20:35:46 -0800611func HandleRequests(db Database, scrape ScrapeMatchList, scoutingServer server.ScoutingServer) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800612 scoutingServer.HandleFunc("/requests", unknown)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800613 scoutingServer.Handle("/requests/submit/data_scouting", submitDataScoutingHandler{db})
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800614 scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800615 scoutingServer.Handle("/requests/request/matches_for_team", requestMatchesForTeamHandler{db})
Philipp Schraderacf96232022-03-01 22:03:30 -0800616 scoutingServer.Handle("/requests/request/data_scouting", requestDataScoutingHandler{db})
Philipp Schraderd3fac192022-03-02 20:35:46 -0800617 scoutingServer.Handle("/requests/refresh_match_list", refreshMatchListHandler{db, scrape})
Alex Perry81f96ba2022-03-13 18:26:19 -0700618 scoutingServer.Handle("/requests/submit/submit_notes", submitNoteScoutingHandler{db})
619 scoutingServer.Handle("/requests/request/notes_for_team", requestNotesForTeamHandler{db})
Milo Lin1d59f0c2022-06-22 20:30:58 -0700620 scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
621 scoutingServer.Handle("/requests/request/shift_schedule", requestShiftScheduleHandler{db})
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800622}