blob: 99b54590cc980aa9671ea6dae1b48053f626dedc [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"
Filip Kujawaf882e022022-12-14 13:14:08 -080018 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_driver_rankings"
19 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_driver_rankings_response"
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080020 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches"
21 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches_response"
Filip Kujawaf882e022022-12-14 13:14:08 -080022 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_notes"
23 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_notes_response"
Philipp Schraderacf96232022-03-01 22:03:30 -080024 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_data_scouting"
25 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_data_scouting_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070026 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team"
27 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070028 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule"
29 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule_response"
Sabina Leaver759090b2023-01-14 20:42:56 -080030 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_actions"
31 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_actions_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080032 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting"
Philipp Schrader30005e42022-03-06 13:53:58 -080033 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
Filip Kujawa210a03b2022-11-24 14:41:11 -080034 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking"
35 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070036 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes"
37 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070038 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule"
39 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080040 "github.com/frc971/971-Robot-Code/scouting/webserver/server"
41 flatbuffers "github.com/google/flatbuffers/go"
42)
43
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080044type SubmitDataScouting = submit_data_scouting.SubmitDataScouting
Philipp Schrader30005e42022-03-06 13:53:58 -080045type SubmitDataScoutingResponseT = submit_data_scouting_response.SubmitDataScoutingResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080046type RequestAllMatches = request_all_matches.RequestAllMatches
47type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
Filip Kujawaf882e022022-12-14 13:14:08 -080048type RequestAllDriverRankings = request_all_driver_rankings.RequestAllDriverRankings
49type RequestAllDriverRankingsResponseT = request_all_driver_rankings_response.RequestAllDriverRankingsResponseT
50type RequestAllNotes = request_all_notes.RequestAllNotes
51type RequestAllNotesResponseT = request_all_notes_response.RequestAllNotesResponseT
Philipp Schraderacf96232022-03-01 22:03:30 -080052type RequestDataScouting = request_data_scouting.RequestDataScouting
53type RequestDataScoutingResponseT = request_data_scouting_response.RequestDataScoutingResponseT
Philipp Schraderd3fac192022-03-02 20:35:46 -080054type RefreshMatchList = refresh_match_list.RefreshMatchList
55type RefreshMatchListResponseT = refresh_match_list_response.RefreshMatchListResponseT
Alex Perry81f96ba2022-03-13 18:26:19 -070056type SubmitNotes = submit_notes.SubmitNotes
57type SubmitNotesResponseT = submit_notes_response.SubmitNotesResponseT
58type RequestNotesForTeam = request_notes_for_team.RequestNotesForTeam
59type RequestNotesForTeamResponseT = request_notes_for_team_response.RequestNotesForTeamResponseT
Milo Lin1d59f0c2022-06-22 20:30:58 -070060type RequestShiftSchedule = request_shift_schedule.RequestShiftSchedule
61type RequestShiftScheduleResponseT = request_shift_schedule_response.RequestShiftScheduleResponseT
62type SubmitShiftSchedule = submit_shift_schedule.SubmitShiftSchedule
63type SubmitShiftScheduleResponseT = submit_shift_schedule_response.SubmitShiftScheduleResponseT
Filip Kujawa210a03b2022-11-24 14:41:11 -080064type SubmitDriverRanking = submit_driver_ranking.SubmitDriverRanking
65type SubmitDriverRankingResponseT = submit_driver_ranking_response.SubmitDriverRankingResponseT
Sabina Leaver759090b2023-01-14 20:42:56 -080066type SubmitActions = submit_actions.SubmitActions
67type SubmitActionsResponseT = submit_actions_response.SubmitActionsResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080068
Philipp Schrader8747f1b2022-02-23 23:56:22 -080069// The interface we expect the database abstraction to conform to.
70// We use an interface here because it makes unit testing easier.
71type Database interface {
72 AddToMatch(db.Match) error
Milo Lin1d59f0c2022-06-22 20:30:58 -070073 AddToShift(db.Shift) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080074 AddToStats(db.Stats) error
75 ReturnMatches() ([]db.Match, error)
Filip Kujawaf882e022022-12-14 13:14:08 -080076 ReturnAllNotes() ([]db.NotesData, error)
77 ReturnAllDriverRankings() ([]db.DriverRankingData, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070078 ReturnAllShifts() ([]db.Shift, error)
Philipp Schrader8747f1b2022-02-23 23:56:22 -080079 ReturnStats() ([]db.Stats, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070080 QueryAllShifts(int) ([]db.Shift, error)
Philipp Schrader8747f1b2022-02-23 23:56:22 -080081 QueryStats(int) ([]db.Stats, error)
Philipp Schradereecb8962022-06-01 21:02:42 -070082 QueryNotes(int32) ([]string, error)
Filip Kujawaf947cb42022-11-21 10:00:30 -080083 AddNotes(db.NotesData) error
Filip Kujawa210a03b2022-11-24 14:41:11 -080084 AddDriverRanking(db.DriverRankingData) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080085}
86
Philipp Schraderd3fac192022-03-02 20:35:46 -080087type ScrapeMatchList func(int32, string) ([]scraping.Match, error)
88
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080089// Handles unknown requests. Just returns a 404.
90func unknown(w http.ResponseWriter, req *http.Request) {
91 w.WriteHeader(http.StatusNotFound)
92}
93
94func respondWithError(w http.ResponseWriter, statusCode int, errorMessage string) {
95 builder := flatbuffers.NewBuilder(1024)
96 builder.Finish((&error_response.ErrorResponseT{
97 ErrorMessage: errorMessage,
98 }).Pack(builder))
99 w.WriteHeader(statusCode)
100 w.Write(builder.FinishedBytes())
101}
102
103func respondNotImplemented(w http.ResponseWriter) {
104 respondWithError(w, http.StatusNotImplemented, "")
105}
106
Philipp Schraderb7e75932022-03-26 16:18:34 -0700107func 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 -0800108 success := true
109 defer func() {
110 if r := recover(); r != nil {
Philipp Schraderb7e75932022-03-26 16:18:34 -0700111 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse %s: %v", requestName, r))
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800112 success = false
113 }
114 }()
Philipp Schraderb7e75932022-03-26 16:18:34 -0700115 result := parser(buf, 0)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800116 return result, success
117}
118
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700119// Parses the authorization information that the browser inserts into the
120// headers. The authorization follows this format:
121//
122// req.Headers["Authorization"] = []string{"Basic <base64 encoded username:password>"}
123func parseUsername(req *http.Request) string {
124 auth, ok := req.Header["Authorization"]
125 if !ok {
126 return "unknown"
127 }
128
129 parts := strings.Split(auth[0], " ")
130 if !(len(parts) == 2 && parts[0] == "Basic") {
131 return "unknown"
132 }
133
134 info, err := base64.StdEncoding.DecodeString(parts[1])
135 if err != nil {
136 log.Println("ERROR: Failed to parse Basic authentication.")
137 return "unknown"
138 }
139
140 loginParts := strings.Split(string(info), ":")
141 if len(loginParts) != 2 {
142 return "unknown"
143 }
144 return loginParts[0]
145}
146
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800147// Handles a SubmitDataScouting request.
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800148type submitDataScoutingHandler struct {
149 db Database
150}
151
152func (handler submitDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700153 // Get the username of the person submitting the data.
154 username := parseUsername(req)
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700155
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800156 requestBytes, err := io.ReadAll(req.Body)
157 if err != nil {
158 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
159 return
160 }
161
Philipp Schraderb7e75932022-03-26 16:18:34 -0700162 request, success := parseRequest[SubmitDataScouting](w, requestBytes, "SubmitDataScouting", submit_data_scouting.GetRootAsSubmitDataScouting)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800163 if !success {
164 return
165 }
166
Philipp Schraderd7b6eba2022-03-18 22:21:25 -0700167 log.Println("Got data scouting data for match", request.Match(), "team", request.Team(), "from", username)
168
Philipp Schrader30005e42022-03-06 13:53:58 -0800169 stats := db.Stats{
Philipp Schraderfee07e12022-03-17 22:19:47 -0700170 TeamNumber: request.Team(),
171 MatchNumber: request.Match(),
Philipp Schrader30b4a682022-04-16 14:36:17 -0700172 SetNumber: request.SetNumber(),
Philipp Schrader4535b7e2022-04-08 20:27:00 -0700173 CompLevel: string(request.CompLevel()),
Philipp Schraderfee07e12022-03-17 22:19:47 -0700174 StartingQuadrant: request.StartingQuadrant(),
175 AutoBallPickedUp: [5]bool{
176 request.AutoBall1(), request.AutoBall2(), request.AutoBall3(),
177 request.AutoBall4(), request.AutoBall5(),
178 },
Philipp Schraderfa45d742022-03-18 19:29:05 -0700179 ShotsMissedAuto: request.MissedShotsAuto(),
180 UpperGoalAuto: request.UpperGoalAuto(),
181 LowerGoalAuto: request.LowerGoalAuto(),
182 ShotsMissed: request.MissedShotsTele(),
183 UpperGoalShots: request.UpperGoalTele(),
184 LowerGoalShots: request.LowerGoalTele(),
185 PlayedDefense: request.DefenseRating(),
186 DefenseReceivedScore: request.DefenseReceivedRating(),
187 Climbing: int32(request.ClimbLevel()),
188 CollectedBy: username,
189 Comment: string(request.Comment()),
Philipp Schrader30005e42022-03-06 13:53:58 -0800190 }
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800191
Philipp Schraderfee07e12022-03-17 22:19:47 -0700192 // Do some error checking.
193 if stats.StartingQuadrant < 1 || stats.StartingQuadrant > 4 {
194 respondWithError(w, http.StatusBadRequest, fmt.Sprint(
195 "Invalid starting_quadrant field value of ", stats.StartingQuadrant))
196 return
197 }
198
Philipp Schrader30005e42022-03-06 13:53:58 -0800199 err = handler.db.AddToStats(stats)
200 if err != nil {
201 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit datascouting data: ", err))
Philipp Schraderfee07e12022-03-17 22:19:47 -0700202 return
Philipp Schrader30005e42022-03-06 13:53:58 -0800203 }
204
205 builder := flatbuffers.NewBuilder(50 * 1024)
206 builder.Finish((&SubmitDataScoutingResponseT{}).Pack(builder))
207 w.Write(builder.FinishedBytes())
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800208}
209
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800210// Handles a RequestAllMaches request.
211type requestAllMatchesHandler struct {
212 db Database
213}
214
215func (handler requestAllMatchesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
216 requestBytes, err := io.ReadAll(req.Body)
217 if err != nil {
218 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
219 return
220 }
221
Philipp Schraderb7e75932022-03-26 16:18:34 -0700222 _, success := parseRequest(w, requestBytes, "RequestAllMatches", request_all_matches.GetRootAsRequestAllMatches)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800223 if !success {
224 return
225 }
226
227 matches, err := handler.db.ReturnMatches()
228 if err != nil {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700229 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800230 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800231 }
232
233 var response RequestAllMatchesResponseT
234 for _, match := range matches {
235 response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
236 MatchNumber: match.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700237 SetNumber: match.SetNumber,
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800238 CompLevel: match.CompLevel,
239 R1: match.R1,
240 R2: match.R2,
241 R3: match.R3,
242 B1: match.B1,
243 B2: match.B2,
244 B3: match.B3,
245 })
246 }
247
248 builder := flatbuffers.NewBuilder(50 * 1024)
249 builder.Finish((&response).Pack(builder))
250 w.Write(builder.FinishedBytes())
251}
252
Philipp Schraderacf96232022-03-01 22:03:30 -0800253// Handles a RequestDataScouting request.
254type requestDataScoutingHandler struct {
255 db Database
256}
257
258func (handler requestDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
259 requestBytes, err := io.ReadAll(req.Body)
260 if err != nil {
261 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
262 return
263 }
264
Philipp Schraderb7e75932022-03-26 16:18:34 -0700265 _, success := parseRequest(w, requestBytes, "RequestDataScouting", request_data_scouting.GetRootAsRequestDataScouting)
Philipp Schraderacf96232022-03-01 22:03:30 -0800266 if !success {
267 return
268 }
269
270 stats, err := handler.db.ReturnStats()
271 if err != nil {
272 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800273 return
Philipp Schraderacf96232022-03-01 22:03:30 -0800274 }
275
276 var response RequestDataScoutingResponseT
277 for _, stat := range stats {
278 response.StatsList = append(response.StatsList, &request_data_scouting_response.StatsT{
Philipp Schraderfa45d742022-03-18 19:29:05 -0700279 Team: stat.TeamNumber,
280 Match: stat.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700281 SetNumber: stat.SetNumber,
Philipp Schrader4535b7e2022-04-08 20:27:00 -0700282 CompLevel: stat.CompLevel,
Philipp Schraderfa45d742022-03-18 19:29:05 -0700283 StartingQuadrant: stat.StartingQuadrant,
284 AutoBall1: stat.AutoBallPickedUp[0],
285 AutoBall2: stat.AutoBallPickedUp[1],
286 AutoBall3: stat.AutoBallPickedUp[2],
287 AutoBall4: stat.AutoBallPickedUp[3],
288 AutoBall5: stat.AutoBallPickedUp[4],
289 MissedShotsAuto: stat.ShotsMissedAuto,
290 UpperGoalAuto: stat.UpperGoalAuto,
291 LowerGoalAuto: stat.LowerGoalAuto,
292 MissedShotsTele: stat.ShotsMissed,
293 UpperGoalTele: stat.UpperGoalShots,
294 LowerGoalTele: stat.LowerGoalShots,
295 DefenseRating: stat.PlayedDefense,
296 DefenseReceivedRating: stat.DefenseReceivedScore,
297 ClimbLevel: request_data_scouting_response.ClimbLevel(stat.Climbing),
298 CollectedBy: stat.CollectedBy,
299 Comment: stat.Comment,
Philipp Schraderacf96232022-03-01 22:03:30 -0800300 })
301 }
302
303 builder := flatbuffers.NewBuilder(50 * 1024)
304 builder.Finish((&response).Pack(builder))
305 w.Write(builder.FinishedBytes())
306}
307
Philipp Schraderd3fac192022-03-02 20:35:46 -0800308func parseTeamKey(teamKey string) (int, error) {
309 // TBA prefixes teams with "frc". Not sure why. Get rid of that.
310 teamKey = strings.TrimPrefix(teamKey, "frc")
Philipp Schrader84fe7782022-11-12 08:10:36 -0800311 magnitude := 0
312 if strings.HasSuffix(teamKey, "A") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800313 magnitude = 0
Philipp Schrader84fe7782022-11-12 08:10:36 -0800314 teamKey = strings.TrimSuffix(teamKey, "A")
315 } else if strings.HasSuffix(teamKey, "B") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800316 magnitude = 9
Philipp Schrader84fe7782022-11-12 08:10:36 -0800317 teamKey = strings.TrimSuffix(teamKey, "B")
318 } else if strings.HasSuffix(teamKey, "C") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800319 magnitude = 8
Philipp Schrader84fe7782022-11-12 08:10:36 -0800320 teamKey = strings.TrimSuffix(teamKey, "C")
321 } else if strings.HasSuffix(teamKey, "D") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800322 magnitude = 7
Philipp Schrader84fe7782022-11-12 08:10:36 -0800323 teamKey = strings.TrimSuffix(teamKey, "D")
324 } else if strings.HasSuffix(teamKey, "E") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800325 magnitude = 6
Philipp Schrader84fe7782022-11-12 08:10:36 -0800326 teamKey = strings.TrimSuffix(teamKey, "E")
327 } else if strings.HasSuffix(teamKey, "F") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800328 magnitude = 5
Philipp Schrader84fe7782022-11-12 08:10:36 -0800329 teamKey = strings.TrimSuffix(teamKey, "F")
330 }
Filip Kujawa4de6feb2022-11-12 08:41:49 -0800331
332 if magnitude != 0 {
333 teamKey = strconv.Itoa(magnitude) + teamKey
Philipp Schrader84fe7782022-11-12 08:10:36 -0800334 }
Filip Kujawa4de6feb2022-11-12 08:41:49 -0800335
336 result, err := strconv.Atoi(teamKey)
Philipp Schrader84fe7782022-11-12 08:10:36 -0800337 return result, err
Philipp Schraderd3fac192022-03-02 20:35:46 -0800338}
339
340// Parses the alliance data from the specified match and returns the three red
341// teams and the three blue teams.
342func parseTeamKeys(match *scraping.Match) ([3]int32, [3]int32, error) {
343 redKeys := match.Alliances.Red.TeamKeys
344 blueKeys := match.Alliances.Blue.TeamKeys
345
346 if len(redKeys) != 3 || len(blueKeys) != 3 {
347 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
348 "Found %d red teams and %d blue teams.", len(redKeys), len(blueKeys)))
349 }
350
351 var red [3]int32
352 for i, key := range redKeys {
353 team, err := parseTeamKey(key)
354 if err != nil {
355 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
356 "Failed to parse red %d team '%s' as integer: %v", i+1, key, err))
357 }
358 red[i] = int32(team)
359 }
360 var blue [3]int32
361 for i, key := range blueKeys {
362 team, err := parseTeamKey(key)
363 if err != nil {
364 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
365 "Failed to parse blue %d team '%s' as integer: %v", i+1, key, err))
366 }
367 blue[i] = int32(team)
368 }
369 return red, blue, nil
370}
371
372type refreshMatchListHandler struct {
373 db Database
374 scrape ScrapeMatchList
375}
376
377func (handler refreshMatchListHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
378 requestBytes, err := io.ReadAll(req.Body)
379 if err != nil {
380 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
381 return
382 }
383
Philipp Schraderb7e75932022-03-26 16:18:34 -0700384 request, success := parseRequest(w, requestBytes, "RefreshMatchList", refresh_match_list.GetRootAsRefreshMatchList)
Philipp Schraderd3fac192022-03-02 20:35:46 -0800385 if !success {
386 return
387 }
388
389 matches, err := handler.scrape(request.Year(), string(request.EventCode()))
390 if err != nil {
391 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to scrape match list: ", err))
392 return
393 }
394
395 for _, match := range matches {
396 // Make sure the data is valid.
397 red, blue, err := parseTeamKeys(&match)
398 if err != nil {
399 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
400 "TheBlueAlliance data for match %d is malformed: %v", match.MatchNumber, err))
401 return
402 }
403 // Add the match to the database.
Philipp Schrader7365d322022-03-06 16:40:08 -0800404 err = handler.db.AddToMatch(db.Match{
Philipp Schraderd3fac192022-03-02 20:35:46 -0800405 MatchNumber: int32(match.MatchNumber),
Philipp Schrader30b4a682022-04-16 14:36:17 -0700406 SetNumber: int32(match.SetNumber),
Philipp Schrader45befdd2022-04-08 19:12:44 -0700407 CompLevel: match.CompLevel,
408 R1: red[0],
409 R2: red[1],
410 R3: red[2],
411 B1: blue[0],
412 B2: blue[1],
413 B3: blue[2],
Philipp Schraderd3fac192022-03-02 20:35:46 -0800414 })
Philipp Schrader7365d322022-03-06 16:40:08 -0800415 if err != nil {
416 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
417 "Failed to add match %d to the database: %v", match.MatchNumber, err))
418 return
419 }
Philipp Schraderd3fac192022-03-02 20:35:46 -0800420 }
421
422 var response RefreshMatchListResponseT
423 builder := flatbuffers.NewBuilder(1024)
424 builder.Finish((&response).Pack(builder))
425 w.Write(builder.FinishedBytes())
426}
427
Alex Perry81f96ba2022-03-13 18:26:19 -0700428type submitNoteScoutingHandler struct {
429 db Database
430}
431
432func (handler submitNoteScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
433 requestBytes, err := io.ReadAll(req.Body)
434 if err != nil {
435 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
436 return
437 }
438
Philipp Schraderb7e75932022-03-26 16:18:34 -0700439 request, success := parseRequest(w, requestBytes, "SubmitNotes", submit_notes.GetRootAsSubmitNotes)
Alex Perry81f96ba2022-03-13 18:26:19 -0700440 if !success {
441 return
442 }
443
Filip Kujawaf947cb42022-11-21 10:00:30 -0800444 err = handler.db.AddNotes(db.NotesData{
445 TeamNumber: request.Team(),
446 Notes: string(request.Notes()),
447 GoodDriving: bool(request.GoodDriving()),
448 BadDriving: bool(request.BadDriving()),
449 SketchyClimb: bool(request.SketchyClimb()),
450 SolidClimb: bool(request.SolidClimb()),
451 GoodDefense: bool(request.GoodDefense()),
452 BadDefense: bool(request.BadDefense()),
453 })
Alex Perry81f96ba2022-03-13 18:26:19 -0700454 if err != nil {
455 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert notes: %v", err))
456 return
457 }
458
459 var response SubmitNotesResponseT
460 builder := flatbuffers.NewBuilder(10)
461 builder.Finish((&response).Pack(builder))
462 w.Write(builder.FinishedBytes())
463}
464
Alex Perry81f96ba2022-03-13 18:26:19 -0700465type requestNotesForTeamHandler struct {
466 db Database
467}
468
469func (handler requestNotesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
470 requestBytes, err := io.ReadAll(req.Body)
471 if err != nil {
472 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
473 return
474 }
475
Philipp Schraderb7e75932022-03-26 16:18:34 -0700476 request, success := parseRequest(w, requestBytes, "RequestNotesForTeam", request_notes_for_team.GetRootAsRequestNotesForTeam)
Alex Perry81f96ba2022-03-13 18:26:19 -0700477 if !success {
478 return
479 }
480
Philipp Schradereecb8962022-06-01 21:02:42 -0700481 notes, err := handler.db.QueryNotes(request.Team())
Alex Perry81f96ba2022-03-13 18:26:19 -0700482 if err != nil {
483 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query notes: %v", err))
484 return
485 }
486
487 var response RequestNotesForTeamResponseT
Philipp Schradereecb8962022-06-01 21:02:42 -0700488 for _, data := range notes {
Alex Perry81f96ba2022-03-13 18:26:19 -0700489 response.Notes = append(response.Notes, &request_notes_for_team_response.NoteT{data})
490 }
491
492 builder := flatbuffers.NewBuilder(1024)
493 builder.Finish((&response).Pack(builder))
494 w.Write(builder.FinishedBytes())
495}
496
Milo Lin1d59f0c2022-06-22 20:30:58 -0700497type requestShiftScheduleHandler struct {
498 db Database
499}
500
501func (handler requestShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
502 requestBytes, err := io.ReadAll(req.Body)
503 if err != nil {
504 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
505 return
506 }
507
508 _, success := parseRequest(w, requestBytes, "RequestShiftSchedule", request_shift_schedule.GetRootAsRequestShiftSchedule)
509 if !success {
510 return
511 }
512
513 shiftData, err := handler.db.ReturnAllShifts()
514 if err != nil {
515 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query shift schedule: %v", err))
516 return
517 }
518
519 var response RequestShiftScheduleResponseT
520 for _, shifts := range shiftData {
521 response.ShiftSchedule = append(response.ShiftSchedule, &request_shift_schedule_response.MatchAssignmentT{
522 MatchNumber: shifts.MatchNumber,
523 R1scouter: shifts.R1scouter,
524 R2scouter: shifts.R2scouter,
525 R3scouter: shifts.R3scouter,
526 B1scouter: shifts.B1scouter,
527 B2scouter: shifts.B2scouter,
528 B3scouter: shifts.B3scouter,
529 })
530 }
531
532 builder := flatbuffers.NewBuilder(1024)
533 builder.Finish((&response).Pack(builder))
534 w.Write(builder.FinishedBytes())
535}
536
537type submitShiftScheduleHandler struct {
538 db Database
539}
540
541func (handler submitShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
542 // Get the username of the person submitting the data.
543 username := parseUsername(req)
544
545 requestBytes, err := io.ReadAll(req.Body)
546 if err != nil {
547 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
548 return
549 }
550
551 request, success := parseRequest[SubmitShiftSchedule](w, requestBytes, "SubmitShiftSchedule", submit_shift_schedule.GetRootAsSubmitShiftSchedule)
552 if !success {
553 return
554 }
555
556 log.Println("Got shift schedule from", username)
557 shift_schedule_length := request.ShiftScheduleLength()
558 for i := 0; i < shift_schedule_length; i++ {
559 var match_assignment submit_shift_schedule.MatchAssignment
560 request.ShiftSchedule(&match_assignment, i)
561 current_shift := db.Shift{
562 MatchNumber: match_assignment.MatchNumber(),
563 R1scouter: string(match_assignment.R1scouter()),
564 R2scouter: string(match_assignment.R2scouter()),
565 R3scouter: string(match_assignment.R3scouter()),
566 B1scouter: string(match_assignment.B1scouter()),
567 B2scouter: string(match_assignment.B2scouter()),
568 B3scouter: string(match_assignment.B3scouter()),
569 }
570 err = handler.db.AddToShift(current_shift)
571 if err != nil {
572 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit shift schedule: ", err))
573 return
574 }
575 }
576
577 builder := flatbuffers.NewBuilder(50 * 1024)
578 builder.Finish((&SubmitShiftScheduleResponseT{}).Pack(builder))
579 w.Write(builder.FinishedBytes())
580}
581
Filip Kujawa210a03b2022-11-24 14:41:11 -0800582type SubmitDriverRankingHandler struct {
583 db Database
584}
585
586func (handler SubmitDriverRankingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
587 requestBytes, err := io.ReadAll(req.Body)
588 if err != nil {
589 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
590 return
591 }
592
593 request, success := parseRequest(w, requestBytes, "SubmitDriverRanking", submit_driver_ranking.GetRootAsSubmitDriverRanking)
594 if !success {
595 return
596 }
597
598 err = handler.db.AddDriverRanking(db.DriverRankingData{
599 MatchNumber: request.MatchNumber(),
600 Rank1: request.Rank1(),
601 Rank2: request.Rank2(),
602 Rank3: request.Rank3(),
603 })
604
605 if err != nil {
606 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert driver ranking: %v", err))
607 return
608 }
609
610 var response SubmitDriverRankingResponseT
611 builder := flatbuffers.NewBuilder(10)
612 builder.Finish((&response).Pack(builder))
613 w.Write(builder.FinishedBytes())
614}
615
Filip Kujawaf882e022022-12-14 13:14:08 -0800616type requestAllNotesHandler struct {
617 db Database
618}
619
620func (handler requestAllNotesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
621 requestBytes, err := io.ReadAll(req.Body)
622 if err != nil {
623 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
624 return
625 }
626
627 _, success := parseRequest(w, requestBytes, "RequestAllNotes", request_all_notes.GetRootAsRequestAllNotes)
628 if !success {
629 return
630 }
631
632 notes, err := handler.db.ReturnAllNotes()
633 if err != nil {
634 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
635 return
636 }
637
638 var response RequestAllNotesResponseT
639 for _, note := range notes {
640 response.NoteList = append(response.NoteList, &request_all_notes_response.NoteT{
641 Team: note.TeamNumber,
642 Notes: note.Notes,
643 GoodDriving: note.GoodDriving,
644 BadDriving: note.BadDriving,
645 SketchyClimb: note.SketchyClimb,
646 SolidClimb: note.SolidClimb,
647 GoodDefense: note.GoodDefense,
648 BadDefense: note.BadDefense,
649 })
650 }
651
652 builder := flatbuffers.NewBuilder(50 * 1024)
653 builder.Finish((&response).Pack(builder))
654 w.Write(builder.FinishedBytes())
655}
656
657type requestAllDriverRankingsHandler struct {
658 db Database
659}
660
661func (handler requestAllDriverRankingsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
662 requestBytes, err := io.ReadAll(req.Body)
663 if err != nil {
664 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
665 return
666 }
667
668 _, success := parseRequest(w, requestBytes, "RequestAllDriverRankings", request_all_driver_rankings.GetRootAsRequestAllDriverRankings)
669 if !success {
670 return
671 }
672
673 rankings, err := handler.db.ReturnAllDriverRankings()
674 if err != nil {
675 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
676 return
677 }
678
679 var response RequestAllDriverRankingsResponseT
680 for _, ranking := range rankings {
681 response.DriverRankingList = append(response.DriverRankingList, &request_all_driver_rankings_response.RankingT{
682 MatchNumber: ranking.MatchNumber,
683 Rank1: ranking.Rank1,
684 Rank2: ranking.Rank2,
685 Rank3: ranking.Rank3,
686 })
687 }
688
689 builder := flatbuffers.NewBuilder(50 * 1024)
690 builder.Finish((&response).Pack(builder))
691 w.Write(builder.FinishedBytes())
692}
693
Philipp Schraderd3fac192022-03-02 20:35:46 -0800694func HandleRequests(db Database, scrape ScrapeMatchList, scoutingServer server.ScoutingServer) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800695 scoutingServer.HandleFunc("/requests", unknown)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800696 scoutingServer.Handle("/requests/submit/data_scouting", submitDataScoutingHandler{db})
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800697 scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
Filip Kujawaf882e022022-12-14 13:14:08 -0800698 scoutingServer.Handle("/requests/request/all_notes", requestAllNotesHandler{db})
699 scoutingServer.Handle("/requests/request/all_driver_rankings", requestAllDriverRankingsHandler{db})
Philipp Schraderacf96232022-03-01 22:03:30 -0800700 scoutingServer.Handle("/requests/request/data_scouting", requestDataScoutingHandler{db})
Philipp Schraderd3fac192022-03-02 20:35:46 -0800701 scoutingServer.Handle("/requests/refresh_match_list", refreshMatchListHandler{db, scrape})
Alex Perry81f96ba2022-03-13 18:26:19 -0700702 scoutingServer.Handle("/requests/submit/submit_notes", submitNoteScoutingHandler{db})
703 scoutingServer.Handle("/requests/request/notes_for_team", requestNotesForTeamHandler{db})
Milo Lin1d59f0c2022-06-22 20:30:58 -0700704 scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
705 scoutingServer.Handle("/requests/request/shift_schedule", requestShiftScheduleHandler{db})
Filip Kujawa210a03b2022-11-24 14:41:11 -0800706 scoutingServer.Handle("/requests/submit/submit_driver_ranking", SubmitDriverRankingHandler{db})
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800707}