blob: 12bc3da0cd90bf9c3cfcf526a68082fd9745db23 [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"
Filip Kujawa210a03b2022-11-24 14:41:11 -080030 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking"
31 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070032 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes"
33 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070034 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule"
35 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080036 "github.com/frc971/971-Robot-Code/scouting/webserver/server"
37 flatbuffers "github.com/google/flatbuffers/go"
38)
39
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080040type SubmitDataScouting = submit_data_scouting.SubmitDataScouting
Philipp Schrader30005e42022-03-06 13:53:58 -080041type SubmitDataScoutingResponseT = submit_data_scouting_response.SubmitDataScoutingResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080042type RequestAllMatches = request_all_matches.RequestAllMatches
43type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080044type RequestMatchesForTeam = request_matches_for_team.RequestMatchesForTeam
45type RequestMatchesForTeamResponseT = request_matches_for_team_response.RequestMatchesForTeamResponseT
Philipp Schraderacf96232022-03-01 22:03:30 -080046type RequestDataScouting = request_data_scouting.RequestDataScouting
47type RequestDataScoutingResponseT = request_data_scouting_response.RequestDataScoutingResponseT
Philipp Schraderd3fac192022-03-02 20:35:46 -080048type RefreshMatchList = refresh_match_list.RefreshMatchList
49type RefreshMatchListResponseT = refresh_match_list_response.RefreshMatchListResponseT
Alex Perry81f96ba2022-03-13 18:26:19 -070050type SubmitNotes = submit_notes.SubmitNotes
51type SubmitNotesResponseT = submit_notes_response.SubmitNotesResponseT
52type RequestNotesForTeam = request_notes_for_team.RequestNotesForTeam
53type RequestNotesForTeamResponseT = request_notes_for_team_response.RequestNotesForTeamResponseT
Milo Lin1d59f0c2022-06-22 20:30:58 -070054type RequestShiftSchedule = request_shift_schedule.RequestShiftSchedule
55type RequestShiftScheduleResponseT = request_shift_schedule_response.RequestShiftScheduleResponseT
56type SubmitShiftSchedule = submit_shift_schedule.SubmitShiftSchedule
57type SubmitShiftScheduleResponseT = submit_shift_schedule_response.SubmitShiftScheduleResponseT
Filip Kujawa210a03b2022-11-24 14:41:11 -080058type SubmitDriverRanking = submit_driver_ranking.SubmitDriverRanking
59type SubmitDriverRankingResponseT = submit_driver_ranking_response.SubmitDriverRankingResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080060
Philipp Schrader8747f1b2022-02-23 23:56:22 -080061// The interface we expect the database abstraction to conform to.
62// We use an interface here because it makes unit testing easier.
63type Database interface {
64 AddToMatch(db.Match) error
Milo Lin1d59f0c2022-06-22 20:30:58 -070065 AddToShift(db.Shift) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080066 AddToStats(db.Stats) error
67 ReturnMatches() ([]db.Match, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070068 ReturnAllShifts() ([]db.Shift, error)
Philipp Schrader8747f1b2022-02-23 23:56:22 -080069 ReturnStats() ([]db.Stats, error)
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080070 QueryMatches(int32) ([]db.Match, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070071 QueryAllShifts(int) ([]db.Shift, error)
Philipp Schrader8747f1b2022-02-23 23:56:22 -080072 QueryStats(int) ([]db.Stats, error)
Philipp Schradereecb8962022-06-01 21:02:42 -070073 QueryNotes(int32) ([]string, error)
Filip Kujawaf947cb42022-11-21 10:00:30 -080074 AddNotes(db.NotesData) error
Filip Kujawa210a03b2022-11-24 14:41:11 -080075 AddDriverRanking(db.DriverRankingData) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080076}
77
Philipp Schraderd3fac192022-03-02 20:35:46 -080078type ScrapeMatchList func(int32, string) ([]scraping.Match, error)
79
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080080// Handles unknown requests. Just returns a 404.
81func unknown(w http.ResponseWriter, req *http.Request) {
82 w.WriteHeader(http.StatusNotFound)
83}
84
85func respondWithError(w http.ResponseWriter, statusCode int, errorMessage string) {
86 builder := flatbuffers.NewBuilder(1024)
87 builder.Finish((&error_response.ErrorResponseT{
88 ErrorMessage: errorMessage,
89 }).Pack(builder))
90 w.WriteHeader(statusCode)
91 w.Write(builder.FinishedBytes())
92}
93
94func respondNotImplemented(w http.ResponseWriter) {
95 respondWithError(w, http.StatusNotImplemented, "")
96}
97
Philipp Schraderb7e75932022-03-26 16:18:34 -070098func 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 -080099 success := true
100 defer func() {
101 if r := recover(); r != nil {
Philipp Schraderb7e75932022-03-26 16:18:34 -0700102 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse %s: %v", requestName, r))
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800103 success = false
104 }
105 }()
Philipp Schraderb7e75932022-03-26 16:18:34 -0700106 result := parser(buf, 0)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800107 return result, success
108}
109
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700110// Parses the authorization information that the browser inserts into the
111// headers. The authorization follows this format:
112//
113// req.Headers["Authorization"] = []string{"Basic <base64 encoded username:password>"}
114func parseUsername(req *http.Request) string {
115 auth, ok := req.Header["Authorization"]
116 if !ok {
117 return "unknown"
118 }
119
120 parts := strings.Split(auth[0], " ")
121 if !(len(parts) == 2 && parts[0] == "Basic") {
122 return "unknown"
123 }
124
125 info, err := base64.StdEncoding.DecodeString(parts[1])
126 if err != nil {
127 log.Println("ERROR: Failed to parse Basic authentication.")
128 return "unknown"
129 }
130
131 loginParts := strings.Split(string(info), ":")
132 if len(loginParts) != 2 {
133 return "unknown"
134 }
135 return loginParts[0]
136}
137
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800138// Handles a SubmitDataScouting request.
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800139type submitDataScoutingHandler struct {
140 db Database
141}
142
143func (handler submitDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700144 // Get the username of the person submitting the data.
145 username := parseUsername(req)
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700146
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800147 requestBytes, err := io.ReadAll(req.Body)
148 if err != nil {
149 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
150 return
151 }
152
Philipp Schraderb7e75932022-03-26 16:18:34 -0700153 request, success := parseRequest[SubmitDataScouting](w, requestBytes, "SubmitDataScouting", submit_data_scouting.GetRootAsSubmitDataScouting)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800154 if !success {
155 return
156 }
157
Philipp Schraderd7b6eba2022-03-18 22:21:25 -0700158 log.Println("Got data scouting data for match", request.Match(), "team", request.Team(), "from", username)
159
Philipp Schrader30005e42022-03-06 13:53:58 -0800160 stats := db.Stats{
Philipp Schraderfee07e12022-03-17 22:19:47 -0700161 TeamNumber: request.Team(),
162 MatchNumber: request.Match(),
Philipp Schrader30b4a682022-04-16 14:36:17 -0700163 SetNumber: request.SetNumber(),
Philipp Schrader4535b7e2022-04-08 20:27:00 -0700164 CompLevel: string(request.CompLevel()),
Philipp Schraderfee07e12022-03-17 22:19:47 -0700165 StartingQuadrant: request.StartingQuadrant(),
166 AutoBallPickedUp: [5]bool{
167 request.AutoBall1(), request.AutoBall2(), request.AutoBall3(),
168 request.AutoBall4(), request.AutoBall5(),
169 },
Philipp Schraderfa45d742022-03-18 19:29:05 -0700170 ShotsMissedAuto: request.MissedShotsAuto(),
171 UpperGoalAuto: request.UpperGoalAuto(),
172 LowerGoalAuto: request.LowerGoalAuto(),
173 ShotsMissed: request.MissedShotsTele(),
174 UpperGoalShots: request.UpperGoalTele(),
175 LowerGoalShots: request.LowerGoalTele(),
176 PlayedDefense: request.DefenseRating(),
177 DefenseReceivedScore: request.DefenseReceivedRating(),
178 Climbing: int32(request.ClimbLevel()),
179 CollectedBy: username,
180 Comment: string(request.Comment()),
Philipp Schrader30005e42022-03-06 13:53:58 -0800181 }
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800182
Philipp Schraderfee07e12022-03-17 22:19:47 -0700183 // Do some error checking.
184 if stats.StartingQuadrant < 1 || stats.StartingQuadrant > 4 {
185 respondWithError(w, http.StatusBadRequest, fmt.Sprint(
186 "Invalid starting_quadrant field value of ", stats.StartingQuadrant))
187 return
188 }
189
Philipp Schrader30005e42022-03-06 13:53:58 -0800190 err = handler.db.AddToStats(stats)
191 if err != nil {
192 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit datascouting data: ", err))
Philipp Schraderfee07e12022-03-17 22:19:47 -0700193 return
Philipp Schrader30005e42022-03-06 13:53:58 -0800194 }
195
196 builder := flatbuffers.NewBuilder(50 * 1024)
197 builder.Finish((&SubmitDataScoutingResponseT{}).Pack(builder))
198 w.Write(builder.FinishedBytes())
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800199}
200
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800201// Handles a RequestAllMaches request.
202type requestAllMatchesHandler struct {
203 db Database
204}
205
206func (handler requestAllMatchesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
207 requestBytes, err := io.ReadAll(req.Body)
208 if err != nil {
209 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
210 return
211 }
212
Philipp Schraderb7e75932022-03-26 16:18:34 -0700213 _, success := parseRequest(w, requestBytes, "RequestAllMatches", request_all_matches.GetRootAsRequestAllMatches)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800214 if !success {
215 return
216 }
217
218 matches, err := handler.db.ReturnMatches()
219 if err != nil {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700220 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800221 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800222 }
223
224 var response RequestAllMatchesResponseT
225 for _, match := range matches {
226 response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
227 MatchNumber: match.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700228 SetNumber: match.SetNumber,
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800229 CompLevel: match.CompLevel,
230 R1: match.R1,
231 R2: match.R2,
232 R3: match.R3,
233 B1: match.B1,
234 B2: match.B2,
235 B3: match.B3,
236 })
237 }
238
239 builder := flatbuffers.NewBuilder(50 * 1024)
240 builder.Finish((&response).Pack(builder))
241 w.Write(builder.FinishedBytes())
242}
243
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800244// Handles a RequestMatchesForTeam request.
245type requestMatchesForTeamHandler struct {
246 db Database
247}
248
249func (handler requestMatchesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
250 requestBytes, err := io.ReadAll(req.Body)
251 if err != nil {
252 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
253 return
254 }
255
Philipp Schraderb7e75932022-03-26 16:18:34 -0700256 request, success := parseRequest(w, requestBytes, "RequestMatchesForTeam", request_matches_for_team.GetRootAsRequestMatchesForTeam)
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800257 if !success {
258 return
259 }
260
261 matches, err := handler.db.QueryMatches(request.Team())
262 if err != nil {
263 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800264 return
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800265 }
266
267 var response RequestAllMatchesResponseT
268 for _, match := range matches {
269 response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
270 MatchNumber: match.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700271 SetNumber: match.SetNumber,
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800272 CompLevel: match.CompLevel,
273 R1: match.R1,
274 R2: match.R2,
275 R3: match.R3,
276 B1: match.B1,
277 B2: match.B2,
278 B3: match.B3,
279 })
280 }
281
282 builder := flatbuffers.NewBuilder(50 * 1024)
283 builder.Finish((&response).Pack(builder))
284 w.Write(builder.FinishedBytes())
285}
286
Philipp Schraderacf96232022-03-01 22:03:30 -0800287// Handles a RequestDataScouting request.
288type requestDataScoutingHandler struct {
289 db Database
290}
291
292func (handler requestDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
293 requestBytes, err := io.ReadAll(req.Body)
294 if err != nil {
295 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
296 return
297 }
298
Philipp Schraderb7e75932022-03-26 16:18:34 -0700299 _, success := parseRequest(w, requestBytes, "RequestDataScouting", request_data_scouting.GetRootAsRequestDataScouting)
Philipp Schraderacf96232022-03-01 22:03:30 -0800300 if !success {
301 return
302 }
303
304 stats, err := handler.db.ReturnStats()
305 if err != nil {
306 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800307 return
Philipp Schraderacf96232022-03-01 22:03:30 -0800308 }
309
310 var response RequestDataScoutingResponseT
311 for _, stat := range stats {
312 response.StatsList = append(response.StatsList, &request_data_scouting_response.StatsT{
Philipp Schraderfa45d742022-03-18 19:29:05 -0700313 Team: stat.TeamNumber,
314 Match: stat.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700315 SetNumber: stat.SetNumber,
Philipp Schrader4535b7e2022-04-08 20:27:00 -0700316 CompLevel: stat.CompLevel,
Philipp Schraderfa45d742022-03-18 19:29:05 -0700317 StartingQuadrant: stat.StartingQuadrant,
318 AutoBall1: stat.AutoBallPickedUp[0],
319 AutoBall2: stat.AutoBallPickedUp[1],
320 AutoBall3: stat.AutoBallPickedUp[2],
321 AutoBall4: stat.AutoBallPickedUp[3],
322 AutoBall5: stat.AutoBallPickedUp[4],
323 MissedShotsAuto: stat.ShotsMissedAuto,
324 UpperGoalAuto: stat.UpperGoalAuto,
325 LowerGoalAuto: stat.LowerGoalAuto,
326 MissedShotsTele: stat.ShotsMissed,
327 UpperGoalTele: stat.UpperGoalShots,
328 LowerGoalTele: stat.LowerGoalShots,
329 DefenseRating: stat.PlayedDefense,
330 DefenseReceivedRating: stat.DefenseReceivedScore,
331 ClimbLevel: request_data_scouting_response.ClimbLevel(stat.Climbing),
332 CollectedBy: stat.CollectedBy,
333 Comment: stat.Comment,
Philipp Schraderacf96232022-03-01 22:03:30 -0800334 })
335 }
336
337 builder := flatbuffers.NewBuilder(50 * 1024)
338 builder.Finish((&response).Pack(builder))
339 w.Write(builder.FinishedBytes())
340}
341
Philipp Schraderd3fac192022-03-02 20:35:46 -0800342func parseTeamKey(teamKey string) (int, error) {
343 // TBA prefixes teams with "frc". Not sure why. Get rid of that.
344 teamKey = strings.TrimPrefix(teamKey, "frc")
Philipp Schrader84fe7782022-11-12 08:10:36 -0800345 magnitude := 0
346 if strings.HasSuffix(teamKey, "A") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800347 magnitude = 0
Philipp Schrader84fe7782022-11-12 08:10:36 -0800348 teamKey = strings.TrimSuffix(teamKey, "A")
349 } else if strings.HasSuffix(teamKey, "B") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800350 magnitude = 9
Philipp Schrader84fe7782022-11-12 08:10:36 -0800351 teamKey = strings.TrimSuffix(teamKey, "B")
352 } else if strings.HasSuffix(teamKey, "C") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800353 magnitude = 8
Philipp Schrader84fe7782022-11-12 08:10:36 -0800354 teamKey = strings.TrimSuffix(teamKey, "C")
355 } else if strings.HasSuffix(teamKey, "D") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800356 magnitude = 7
Philipp Schrader84fe7782022-11-12 08:10:36 -0800357 teamKey = strings.TrimSuffix(teamKey, "D")
358 } else if strings.HasSuffix(teamKey, "E") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800359 magnitude = 6
Philipp Schrader84fe7782022-11-12 08:10:36 -0800360 teamKey = strings.TrimSuffix(teamKey, "E")
361 } else if strings.HasSuffix(teamKey, "F") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800362 magnitude = 5
Philipp Schrader84fe7782022-11-12 08:10:36 -0800363 teamKey = strings.TrimSuffix(teamKey, "F")
364 }
Filip Kujawa4de6feb2022-11-12 08:41:49 -0800365
366 if magnitude != 0 {
367 teamKey = strconv.Itoa(magnitude) + teamKey
Philipp Schrader84fe7782022-11-12 08:10:36 -0800368 }
Filip Kujawa4de6feb2022-11-12 08:41:49 -0800369
370 result, err := strconv.Atoi(teamKey)
Philipp Schrader84fe7782022-11-12 08:10:36 -0800371 return result, err
Philipp Schraderd3fac192022-03-02 20:35:46 -0800372}
373
374// Parses the alliance data from the specified match and returns the three red
375// teams and the three blue teams.
376func parseTeamKeys(match *scraping.Match) ([3]int32, [3]int32, error) {
377 redKeys := match.Alliances.Red.TeamKeys
378 blueKeys := match.Alliances.Blue.TeamKeys
379
380 if len(redKeys) != 3 || len(blueKeys) != 3 {
381 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
382 "Found %d red teams and %d blue teams.", len(redKeys), len(blueKeys)))
383 }
384
385 var red [3]int32
386 for i, key := range redKeys {
387 team, err := parseTeamKey(key)
388 if err != nil {
389 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
390 "Failed to parse red %d team '%s' as integer: %v", i+1, key, err))
391 }
392 red[i] = int32(team)
393 }
394 var blue [3]int32
395 for i, key := range blueKeys {
396 team, err := parseTeamKey(key)
397 if err != nil {
398 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
399 "Failed to parse blue %d team '%s' as integer: %v", i+1, key, err))
400 }
401 blue[i] = int32(team)
402 }
403 return red, blue, nil
404}
405
406type refreshMatchListHandler struct {
407 db Database
408 scrape ScrapeMatchList
409}
410
411func (handler refreshMatchListHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
412 requestBytes, err := io.ReadAll(req.Body)
413 if err != nil {
414 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
415 return
416 }
417
Philipp Schraderb7e75932022-03-26 16:18:34 -0700418 request, success := parseRequest(w, requestBytes, "RefreshMatchList", refresh_match_list.GetRootAsRefreshMatchList)
Philipp Schraderd3fac192022-03-02 20:35:46 -0800419 if !success {
420 return
421 }
422
423 matches, err := handler.scrape(request.Year(), string(request.EventCode()))
424 if err != nil {
425 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to scrape match list: ", err))
426 return
427 }
428
429 for _, match := range matches {
430 // Make sure the data is valid.
431 red, blue, err := parseTeamKeys(&match)
432 if err != nil {
433 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
434 "TheBlueAlliance data for match %d is malformed: %v", match.MatchNumber, err))
435 return
436 }
437 // Add the match to the database.
Philipp Schrader7365d322022-03-06 16:40:08 -0800438 err = handler.db.AddToMatch(db.Match{
Philipp Schraderd3fac192022-03-02 20:35:46 -0800439 MatchNumber: int32(match.MatchNumber),
Philipp Schrader30b4a682022-04-16 14:36:17 -0700440 SetNumber: int32(match.SetNumber),
Philipp Schrader45befdd2022-04-08 19:12:44 -0700441 CompLevel: match.CompLevel,
442 R1: red[0],
443 R2: red[1],
444 R3: red[2],
445 B1: blue[0],
446 B2: blue[1],
447 B3: blue[2],
Philipp Schraderd3fac192022-03-02 20:35:46 -0800448 })
Philipp Schrader7365d322022-03-06 16:40:08 -0800449 if err != nil {
450 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
451 "Failed to add match %d to the database: %v", match.MatchNumber, err))
452 return
453 }
Philipp Schraderd3fac192022-03-02 20:35:46 -0800454 }
455
456 var response RefreshMatchListResponseT
457 builder := flatbuffers.NewBuilder(1024)
458 builder.Finish((&response).Pack(builder))
459 w.Write(builder.FinishedBytes())
460}
461
Alex Perry81f96ba2022-03-13 18:26:19 -0700462type submitNoteScoutingHandler struct {
463 db Database
464}
465
466func (handler submitNoteScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
467 requestBytes, err := io.ReadAll(req.Body)
468 if err != nil {
469 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
470 return
471 }
472
Philipp Schraderb7e75932022-03-26 16:18:34 -0700473 request, success := parseRequest(w, requestBytes, "SubmitNotes", submit_notes.GetRootAsSubmitNotes)
Alex Perry81f96ba2022-03-13 18:26:19 -0700474 if !success {
475 return
476 }
477
Filip Kujawaf947cb42022-11-21 10:00:30 -0800478 err = handler.db.AddNotes(db.NotesData{
479 TeamNumber: request.Team(),
480 Notes: string(request.Notes()),
481 GoodDriving: bool(request.GoodDriving()),
482 BadDriving: bool(request.BadDriving()),
483 SketchyClimb: bool(request.SketchyClimb()),
484 SolidClimb: bool(request.SolidClimb()),
485 GoodDefense: bool(request.GoodDefense()),
486 BadDefense: bool(request.BadDefense()),
487 })
Alex Perry81f96ba2022-03-13 18:26:19 -0700488 if err != nil {
489 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert notes: %v", err))
490 return
491 }
492
493 var response SubmitNotesResponseT
494 builder := flatbuffers.NewBuilder(10)
495 builder.Finish((&response).Pack(builder))
496 w.Write(builder.FinishedBytes())
497}
498
Alex Perry81f96ba2022-03-13 18:26:19 -0700499type requestNotesForTeamHandler struct {
500 db Database
501}
502
503func (handler requestNotesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
504 requestBytes, err := io.ReadAll(req.Body)
505 if err != nil {
506 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
507 return
508 }
509
Philipp Schraderb7e75932022-03-26 16:18:34 -0700510 request, success := parseRequest(w, requestBytes, "RequestNotesForTeam", request_notes_for_team.GetRootAsRequestNotesForTeam)
Alex Perry81f96ba2022-03-13 18:26:19 -0700511 if !success {
512 return
513 }
514
Philipp Schradereecb8962022-06-01 21:02:42 -0700515 notes, err := handler.db.QueryNotes(request.Team())
Alex Perry81f96ba2022-03-13 18:26:19 -0700516 if err != nil {
517 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query notes: %v", err))
518 return
519 }
520
521 var response RequestNotesForTeamResponseT
Philipp Schradereecb8962022-06-01 21:02:42 -0700522 for _, data := range notes {
Alex Perry81f96ba2022-03-13 18:26:19 -0700523 response.Notes = append(response.Notes, &request_notes_for_team_response.NoteT{data})
524 }
525
526 builder := flatbuffers.NewBuilder(1024)
527 builder.Finish((&response).Pack(builder))
528 w.Write(builder.FinishedBytes())
529}
530
Milo Lin1d59f0c2022-06-22 20:30:58 -0700531type requestShiftScheduleHandler struct {
532 db Database
533}
534
535func (handler requestShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
536 requestBytes, err := io.ReadAll(req.Body)
537 if err != nil {
538 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
539 return
540 }
541
542 _, success := parseRequest(w, requestBytes, "RequestShiftSchedule", request_shift_schedule.GetRootAsRequestShiftSchedule)
543 if !success {
544 return
545 }
546
547 shiftData, err := handler.db.ReturnAllShifts()
548 if err != nil {
549 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query shift schedule: %v", err))
550 return
551 }
552
553 var response RequestShiftScheduleResponseT
554 for _, shifts := range shiftData {
555 response.ShiftSchedule = append(response.ShiftSchedule, &request_shift_schedule_response.MatchAssignmentT{
556 MatchNumber: shifts.MatchNumber,
557 R1scouter: shifts.R1scouter,
558 R2scouter: shifts.R2scouter,
559 R3scouter: shifts.R3scouter,
560 B1scouter: shifts.B1scouter,
561 B2scouter: shifts.B2scouter,
562 B3scouter: shifts.B3scouter,
563 })
564 }
565
566 builder := flatbuffers.NewBuilder(1024)
567 builder.Finish((&response).Pack(builder))
568 w.Write(builder.FinishedBytes())
569}
570
571type submitShiftScheduleHandler struct {
572 db Database
573}
574
575func (handler submitShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
576 // Get the username of the person submitting the data.
577 username := parseUsername(req)
578
579 requestBytes, err := io.ReadAll(req.Body)
580 if err != nil {
581 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
582 return
583 }
584
585 request, success := parseRequest[SubmitShiftSchedule](w, requestBytes, "SubmitShiftSchedule", submit_shift_schedule.GetRootAsSubmitShiftSchedule)
586 if !success {
587 return
588 }
589
590 log.Println("Got shift schedule from", username)
591 shift_schedule_length := request.ShiftScheduleLength()
592 for i := 0; i < shift_schedule_length; i++ {
593 var match_assignment submit_shift_schedule.MatchAssignment
594 request.ShiftSchedule(&match_assignment, i)
595 current_shift := db.Shift{
596 MatchNumber: match_assignment.MatchNumber(),
597 R1scouter: string(match_assignment.R1scouter()),
598 R2scouter: string(match_assignment.R2scouter()),
599 R3scouter: string(match_assignment.R3scouter()),
600 B1scouter: string(match_assignment.B1scouter()),
601 B2scouter: string(match_assignment.B2scouter()),
602 B3scouter: string(match_assignment.B3scouter()),
603 }
604 err = handler.db.AddToShift(current_shift)
605 if err != nil {
606 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit shift schedule: ", err))
607 return
608 }
609 }
610
611 builder := flatbuffers.NewBuilder(50 * 1024)
612 builder.Finish((&SubmitShiftScheduleResponseT{}).Pack(builder))
613 w.Write(builder.FinishedBytes())
614}
615
Filip Kujawa210a03b2022-11-24 14:41:11 -0800616type SubmitDriverRankingHandler struct {
617 db Database
618}
619
620func (handler SubmitDriverRankingHandler) 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 request, success := parseRequest(w, requestBytes, "SubmitDriverRanking", submit_driver_ranking.GetRootAsSubmitDriverRanking)
628 if !success {
629 return
630 }
631
632 err = handler.db.AddDriverRanking(db.DriverRankingData{
633 MatchNumber: request.MatchNumber(),
634 Rank1: request.Rank1(),
635 Rank2: request.Rank2(),
636 Rank3: request.Rank3(),
637 })
638
639 if err != nil {
640 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert driver ranking: %v", err))
641 return
642 }
643
644 var response SubmitDriverRankingResponseT
645 builder := flatbuffers.NewBuilder(10)
646 builder.Finish((&response).Pack(builder))
647 w.Write(builder.FinishedBytes())
648}
649
Philipp Schraderd3fac192022-03-02 20:35:46 -0800650func HandleRequests(db Database, scrape ScrapeMatchList, scoutingServer server.ScoutingServer) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800651 scoutingServer.HandleFunc("/requests", unknown)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800652 scoutingServer.Handle("/requests/submit/data_scouting", submitDataScoutingHandler{db})
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800653 scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800654 scoutingServer.Handle("/requests/request/matches_for_team", requestMatchesForTeamHandler{db})
Philipp Schraderacf96232022-03-01 22:03:30 -0800655 scoutingServer.Handle("/requests/request/data_scouting", requestDataScoutingHandler{db})
Philipp Schraderd3fac192022-03-02 20:35:46 -0800656 scoutingServer.Handle("/requests/refresh_match_list", refreshMatchListHandler{db, scrape})
Alex Perry81f96ba2022-03-13 18:26:19 -0700657 scoutingServer.Handle("/requests/submit/submit_notes", submitNoteScoutingHandler{db})
658 scoutingServer.Handle("/requests/request/notes_for_team", requestNotesForTeamHandler{db})
Milo Lin1d59f0c2022-06-22 20:30:58 -0700659 scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
660 scoutingServer.Handle("/requests/request/shift_schedule", requestShiftScheduleHandler{db})
Filip Kujawa210a03b2022-11-24 14:41:11 -0800661 scoutingServer.Handle("/requests/submit/submit_driver_ranking", SubmitDriverRankingHandler{db})
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800662}