blob: 3e567b815168dcd7564c0f1da7d71538925e96cb [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"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080030 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting"
Philipp Schrader30005e42022-03-06 13:53:58 -080031 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
Filip Kujawa210a03b2022-11-24 14:41:11 -080032 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking"
33 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070034 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes"
35 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070036 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule"
37 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080038 "github.com/frc971/971-Robot-Code/scouting/webserver/server"
39 flatbuffers "github.com/google/flatbuffers/go"
40)
41
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080042type SubmitDataScouting = submit_data_scouting.SubmitDataScouting
Philipp Schrader30005e42022-03-06 13:53:58 -080043type SubmitDataScoutingResponseT = submit_data_scouting_response.SubmitDataScoutingResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080044type RequestAllMatches = request_all_matches.RequestAllMatches
45type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
Filip Kujawaf882e022022-12-14 13:14:08 -080046type RequestAllDriverRankings = request_all_driver_rankings.RequestAllDriverRankings
47type RequestAllDriverRankingsResponseT = request_all_driver_rankings_response.RequestAllDriverRankingsResponseT
48type RequestAllNotes = request_all_notes.RequestAllNotes
49type RequestAllNotesResponseT = request_all_notes_response.RequestAllNotesResponseT
Philipp Schraderacf96232022-03-01 22:03:30 -080050type RequestDataScouting = request_data_scouting.RequestDataScouting
51type RequestDataScoutingResponseT = request_data_scouting_response.RequestDataScoutingResponseT
Philipp Schraderd3fac192022-03-02 20:35:46 -080052type RefreshMatchList = refresh_match_list.RefreshMatchList
53type RefreshMatchListResponseT = refresh_match_list_response.RefreshMatchListResponseT
Alex Perry81f96ba2022-03-13 18:26:19 -070054type SubmitNotes = submit_notes.SubmitNotes
55type SubmitNotesResponseT = submit_notes_response.SubmitNotesResponseT
56type RequestNotesForTeam = request_notes_for_team.RequestNotesForTeam
57type RequestNotesForTeamResponseT = request_notes_for_team_response.RequestNotesForTeamResponseT
Milo Lin1d59f0c2022-06-22 20:30:58 -070058type RequestShiftSchedule = request_shift_schedule.RequestShiftSchedule
59type RequestShiftScheduleResponseT = request_shift_schedule_response.RequestShiftScheduleResponseT
60type SubmitShiftSchedule = submit_shift_schedule.SubmitShiftSchedule
61type SubmitShiftScheduleResponseT = submit_shift_schedule_response.SubmitShiftScheduleResponseT
Filip Kujawa210a03b2022-11-24 14:41:11 -080062type SubmitDriverRanking = submit_driver_ranking.SubmitDriverRanking
63type SubmitDriverRankingResponseT = submit_driver_ranking_response.SubmitDriverRankingResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080064
Philipp Schrader8747f1b2022-02-23 23:56:22 -080065// The interface we expect the database abstraction to conform to.
66// We use an interface here because it makes unit testing easier.
67type Database interface {
68 AddToMatch(db.Match) error
Milo Lin1d59f0c2022-06-22 20:30:58 -070069 AddToShift(db.Shift) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080070 AddToStats(db.Stats) error
71 ReturnMatches() ([]db.Match, error)
Filip Kujawaf882e022022-12-14 13:14:08 -080072 ReturnAllNotes() ([]db.NotesData, error)
73 ReturnAllDriverRankings() ([]db.DriverRankingData, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070074 ReturnAllShifts() ([]db.Shift, error)
Philipp Schrader8747f1b2022-02-23 23:56:22 -080075 ReturnStats() ([]db.Stats, error)
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080076 QueryMatches(int32) ([]db.Match, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070077 QueryAllShifts(int) ([]db.Shift, error)
Philipp Schrader8747f1b2022-02-23 23:56:22 -080078 QueryStats(int) ([]db.Stats, error)
Philipp Schradereecb8962022-06-01 21:02:42 -070079 QueryNotes(int32) ([]string, error)
Filip Kujawaf947cb42022-11-21 10:00:30 -080080 AddNotes(db.NotesData) error
Filip Kujawa210a03b2022-11-24 14:41:11 -080081 AddDriverRanking(db.DriverRankingData) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080082}
83
Philipp Schraderd3fac192022-03-02 20:35:46 -080084type ScrapeMatchList func(int32, string) ([]scraping.Match, error)
85
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080086// Handles unknown requests. Just returns a 404.
87func unknown(w http.ResponseWriter, req *http.Request) {
88 w.WriteHeader(http.StatusNotFound)
89}
90
91func respondWithError(w http.ResponseWriter, statusCode int, errorMessage string) {
92 builder := flatbuffers.NewBuilder(1024)
93 builder.Finish((&error_response.ErrorResponseT{
94 ErrorMessage: errorMessage,
95 }).Pack(builder))
96 w.WriteHeader(statusCode)
97 w.Write(builder.FinishedBytes())
98}
99
100func respondNotImplemented(w http.ResponseWriter) {
101 respondWithError(w, http.StatusNotImplemented, "")
102}
103
Philipp Schraderb7e75932022-03-26 16:18:34 -0700104func 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 -0800105 success := true
106 defer func() {
107 if r := recover(); r != nil {
Philipp Schraderb7e75932022-03-26 16:18:34 -0700108 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse %s: %v", requestName, r))
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800109 success = false
110 }
111 }()
Philipp Schraderb7e75932022-03-26 16:18:34 -0700112 result := parser(buf, 0)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800113 return result, success
114}
115
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700116// Parses the authorization information that the browser inserts into the
117// headers. The authorization follows this format:
118//
119// req.Headers["Authorization"] = []string{"Basic <base64 encoded username:password>"}
120func parseUsername(req *http.Request) string {
121 auth, ok := req.Header["Authorization"]
122 if !ok {
123 return "unknown"
124 }
125
126 parts := strings.Split(auth[0], " ")
127 if !(len(parts) == 2 && parts[0] == "Basic") {
128 return "unknown"
129 }
130
131 info, err := base64.StdEncoding.DecodeString(parts[1])
132 if err != nil {
133 log.Println("ERROR: Failed to parse Basic authentication.")
134 return "unknown"
135 }
136
137 loginParts := strings.Split(string(info), ":")
138 if len(loginParts) != 2 {
139 return "unknown"
140 }
141 return loginParts[0]
142}
143
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800144// Handles a SubmitDataScouting request.
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800145type submitDataScoutingHandler struct {
146 db Database
147}
148
149func (handler submitDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700150 // Get the username of the person submitting the data.
151 username := parseUsername(req)
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700152
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800153 requestBytes, err := io.ReadAll(req.Body)
154 if err != nil {
155 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
156 return
157 }
158
Philipp Schraderb7e75932022-03-26 16:18:34 -0700159 request, success := parseRequest[SubmitDataScouting](w, requestBytes, "SubmitDataScouting", submit_data_scouting.GetRootAsSubmitDataScouting)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800160 if !success {
161 return
162 }
163
Philipp Schraderd7b6eba2022-03-18 22:21:25 -0700164 log.Println("Got data scouting data for match", request.Match(), "team", request.Team(), "from", username)
165
Philipp Schrader30005e42022-03-06 13:53:58 -0800166 stats := db.Stats{
Philipp Schraderfee07e12022-03-17 22:19:47 -0700167 TeamNumber: request.Team(),
168 MatchNumber: request.Match(),
Philipp Schrader30b4a682022-04-16 14:36:17 -0700169 SetNumber: request.SetNumber(),
Philipp Schrader4535b7e2022-04-08 20:27:00 -0700170 CompLevel: string(request.CompLevel()),
Philipp Schraderfee07e12022-03-17 22:19:47 -0700171 StartingQuadrant: request.StartingQuadrant(),
172 AutoBallPickedUp: [5]bool{
173 request.AutoBall1(), request.AutoBall2(), request.AutoBall3(),
174 request.AutoBall4(), request.AutoBall5(),
175 },
Philipp Schraderfa45d742022-03-18 19:29:05 -0700176 ShotsMissedAuto: request.MissedShotsAuto(),
177 UpperGoalAuto: request.UpperGoalAuto(),
178 LowerGoalAuto: request.LowerGoalAuto(),
179 ShotsMissed: request.MissedShotsTele(),
180 UpperGoalShots: request.UpperGoalTele(),
181 LowerGoalShots: request.LowerGoalTele(),
182 PlayedDefense: request.DefenseRating(),
183 DefenseReceivedScore: request.DefenseReceivedRating(),
184 Climbing: int32(request.ClimbLevel()),
185 CollectedBy: username,
186 Comment: string(request.Comment()),
Philipp Schrader30005e42022-03-06 13:53:58 -0800187 }
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800188
Philipp Schraderfee07e12022-03-17 22:19:47 -0700189 // Do some error checking.
190 if stats.StartingQuadrant < 1 || stats.StartingQuadrant > 4 {
191 respondWithError(w, http.StatusBadRequest, fmt.Sprint(
192 "Invalid starting_quadrant field value of ", stats.StartingQuadrant))
193 return
194 }
195
Philipp Schrader30005e42022-03-06 13:53:58 -0800196 err = handler.db.AddToStats(stats)
197 if err != nil {
198 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit datascouting data: ", err))
Philipp Schraderfee07e12022-03-17 22:19:47 -0700199 return
Philipp Schrader30005e42022-03-06 13:53:58 -0800200 }
201
202 builder := flatbuffers.NewBuilder(50 * 1024)
203 builder.Finish((&SubmitDataScoutingResponseT{}).Pack(builder))
204 w.Write(builder.FinishedBytes())
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800205}
206
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800207// Handles a RequestAllMaches request.
208type requestAllMatchesHandler struct {
209 db Database
210}
211
212func (handler requestAllMatchesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
213 requestBytes, err := io.ReadAll(req.Body)
214 if err != nil {
215 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
216 return
217 }
218
Philipp Schraderb7e75932022-03-26 16:18:34 -0700219 _, success := parseRequest(w, requestBytes, "RequestAllMatches", request_all_matches.GetRootAsRequestAllMatches)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800220 if !success {
221 return
222 }
223
224 matches, err := handler.db.ReturnMatches()
225 if err != nil {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700226 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800227 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800228 }
229
230 var response RequestAllMatchesResponseT
231 for _, match := range matches {
232 response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
233 MatchNumber: match.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700234 SetNumber: match.SetNumber,
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800235 CompLevel: match.CompLevel,
236 R1: match.R1,
237 R2: match.R2,
238 R3: match.R3,
239 B1: match.B1,
240 B2: match.B2,
241 B3: match.B3,
242 })
243 }
244
245 builder := flatbuffers.NewBuilder(50 * 1024)
246 builder.Finish((&response).Pack(builder))
247 w.Write(builder.FinishedBytes())
248}
249
Philipp Schraderacf96232022-03-01 22:03:30 -0800250// Handles a RequestDataScouting request.
251type requestDataScoutingHandler struct {
252 db Database
253}
254
255func (handler requestDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
256 requestBytes, err := io.ReadAll(req.Body)
257 if err != nil {
258 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
259 return
260 }
261
Philipp Schraderb7e75932022-03-26 16:18:34 -0700262 _, success := parseRequest(w, requestBytes, "RequestDataScouting", request_data_scouting.GetRootAsRequestDataScouting)
Philipp Schraderacf96232022-03-01 22:03:30 -0800263 if !success {
264 return
265 }
266
267 stats, err := handler.db.ReturnStats()
268 if err != nil {
269 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800270 return
Philipp Schraderacf96232022-03-01 22:03:30 -0800271 }
272
273 var response RequestDataScoutingResponseT
274 for _, stat := range stats {
275 response.StatsList = append(response.StatsList, &request_data_scouting_response.StatsT{
Philipp Schraderfa45d742022-03-18 19:29:05 -0700276 Team: stat.TeamNumber,
277 Match: stat.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700278 SetNumber: stat.SetNumber,
Philipp Schrader4535b7e2022-04-08 20:27:00 -0700279 CompLevel: stat.CompLevel,
Philipp Schraderfa45d742022-03-18 19:29:05 -0700280 StartingQuadrant: stat.StartingQuadrant,
281 AutoBall1: stat.AutoBallPickedUp[0],
282 AutoBall2: stat.AutoBallPickedUp[1],
283 AutoBall3: stat.AutoBallPickedUp[2],
284 AutoBall4: stat.AutoBallPickedUp[3],
285 AutoBall5: stat.AutoBallPickedUp[4],
286 MissedShotsAuto: stat.ShotsMissedAuto,
287 UpperGoalAuto: stat.UpperGoalAuto,
288 LowerGoalAuto: stat.LowerGoalAuto,
289 MissedShotsTele: stat.ShotsMissed,
290 UpperGoalTele: stat.UpperGoalShots,
291 LowerGoalTele: stat.LowerGoalShots,
292 DefenseRating: stat.PlayedDefense,
293 DefenseReceivedRating: stat.DefenseReceivedScore,
294 ClimbLevel: request_data_scouting_response.ClimbLevel(stat.Climbing),
295 CollectedBy: stat.CollectedBy,
296 Comment: stat.Comment,
Philipp Schraderacf96232022-03-01 22:03:30 -0800297 })
298 }
299
300 builder := flatbuffers.NewBuilder(50 * 1024)
301 builder.Finish((&response).Pack(builder))
302 w.Write(builder.FinishedBytes())
303}
304
Philipp Schraderd3fac192022-03-02 20:35:46 -0800305func parseTeamKey(teamKey string) (int, error) {
306 // TBA prefixes teams with "frc". Not sure why. Get rid of that.
307 teamKey = strings.TrimPrefix(teamKey, "frc")
Philipp Schrader84fe7782022-11-12 08:10:36 -0800308 magnitude := 0
309 if strings.HasSuffix(teamKey, "A") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800310 magnitude = 0
Philipp Schrader84fe7782022-11-12 08:10:36 -0800311 teamKey = strings.TrimSuffix(teamKey, "A")
312 } else if strings.HasSuffix(teamKey, "B") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800313 magnitude = 9
Philipp Schrader84fe7782022-11-12 08:10:36 -0800314 teamKey = strings.TrimSuffix(teamKey, "B")
315 } else if strings.HasSuffix(teamKey, "C") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800316 magnitude = 8
Philipp Schrader84fe7782022-11-12 08:10:36 -0800317 teamKey = strings.TrimSuffix(teamKey, "C")
318 } else if strings.HasSuffix(teamKey, "D") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800319 magnitude = 7
Philipp Schrader84fe7782022-11-12 08:10:36 -0800320 teamKey = strings.TrimSuffix(teamKey, "D")
321 } else if strings.HasSuffix(teamKey, "E") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800322 magnitude = 6
Philipp Schrader84fe7782022-11-12 08:10:36 -0800323 teamKey = strings.TrimSuffix(teamKey, "E")
324 } else if strings.HasSuffix(teamKey, "F") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800325 magnitude = 5
Philipp Schrader84fe7782022-11-12 08:10:36 -0800326 teamKey = strings.TrimSuffix(teamKey, "F")
327 }
Filip Kujawa4de6feb2022-11-12 08:41:49 -0800328
329 if magnitude != 0 {
330 teamKey = strconv.Itoa(magnitude) + teamKey
Philipp Schrader84fe7782022-11-12 08:10:36 -0800331 }
Filip Kujawa4de6feb2022-11-12 08:41:49 -0800332
333 result, err := strconv.Atoi(teamKey)
Philipp Schrader84fe7782022-11-12 08:10:36 -0800334 return result, err
Philipp Schraderd3fac192022-03-02 20:35:46 -0800335}
336
337// Parses the alliance data from the specified match and returns the three red
338// teams and the three blue teams.
339func parseTeamKeys(match *scraping.Match) ([3]int32, [3]int32, error) {
340 redKeys := match.Alliances.Red.TeamKeys
341 blueKeys := match.Alliances.Blue.TeamKeys
342
343 if len(redKeys) != 3 || len(blueKeys) != 3 {
344 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
345 "Found %d red teams and %d blue teams.", len(redKeys), len(blueKeys)))
346 }
347
348 var red [3]int32
349 for i, key := range redKeys {
350 team, err := parseTeamKey(key)
351 if err != nil {
352 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
353 "Failed to parse red %d team '%s' as integer: %v", i+1, key, err))
354 }
355 red[i] = int32(team)
356 }
357 var blue [3]int32
358 for i, key := range blueKeys {
359 team, err := parseTeamKey(key)
360 if err != nil {
361 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
362 "Failed to parse blue %d team '%s' as integer: %v", i+1, key, err))
363 }
364 blue[i] = int32(team)
365 }
366 return red, blue, nil
367}
368
369type refreshMatchListHandler struct {
370 db Database
371 scrape ScrapeMatchList
372}
373
374func (handler refreshMatchListHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
375 requestBytes, err := io.ReadAll(req.Body)
376 if err != nil {
377 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
378 return
379 }
380
Philipp Schraderb7e75932022-03-26 16:18:34 -0700381 request, success := parseRequest(w, requestBytes, "RefreshMatchList", refresh_match_list.GetRootAsRefreshMatchList)
Philipp Schraderd3fac192022-03-02 20:35:46 -0800382 if !success {
383 return
384 }
385
386 matches, err := handler.scrape(request.Year(), string(request.EventCode()))
387 if err != nil {
388 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to scrape match list: ", err))
389 return
390 }
391
392 for _, match := range matches {
393 // Make sure the data is valid.
394 red, blue, err := parseTeamKeys(&match)
395 if err != nil {
396 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
397 "TheBlueAlliance data for match %d is malformed: %v", match.MatchNumber, err))
398 return
399 }
400 // Add the match to the database.
Philipp Schrader7365d322022-03-06 16:40:08 -0800401 err = handler.db.AddToMatch(db.Match{
Philipp Schraderd3fac192022-03-02 20:35:46 -0800402 MatchNumber: int32(match.MatchNumber),
Philipp Schrader30b4a682022-04-16 14:36:17 -0700403 SetNumber: int32(match.SetNumber),
Philipp Schrader45befdd2022-04-08 19:12:44 -0700404 CompLevel: match.CompLevel,
405 R1: red[0],
406 R2: red[1],
407 R3: red[2],
408 B1: blue[0],
409 B2: blue[1],
410 B3: blue[2],
Philipp Schraderd3fac192022-03-02 20:35:46 -0800411 })
Philipp Schrader7365d322022-03-06 16:40:08 -0800412 if err != nil {
413 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
414 "Failed to add match %d to the database: %v", match.MatchNumber, err))
415 return
416 }
Philipp Schraderd3fac192022-03-02 20:35:46 -0800417 }
418
419 var response RefreshMatchListResponseT
420 builder := flatbuffers.NewBuilder(1024)
421 builder.Finish((&response).Pack(builder))
422 w.Write(builder.FinishedBytes())
423}
424
Alex Perry81f96ba2022-03-13 18:26:19 -0700425type submitNoteScoutingHandler struct {
426 db Database
427}
428
429func (handler submitNoteScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
430 requestBytes, err := io.ReadAll(req.Body)
431 if err != nil {
432 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
433 return
434 }
435
Philipp Schraderb7e75932022-03-26 16:18:34 -0700436 request, success := parseRequest(w, requestBytes, "SubmitNotes", submit_notes.GetRootAsSubmitNotes)
Alex Perry81f96ba2022-03-13 18:26:19 -0700437 if !success {
438 return
439 }
440
Filip Kujawaf947cb42022-11-21 10:00:30 -0800441 err = handler.db.AddNotes(db.NotesData{
442 TeamNumber: request.Team(),
443 Notes: string(request.Notes()),
444 GoodDriving: bool(request.GoodDriving()),
445 BadDriving: bool(request.BadDriving()),
446 SketchyClimb: bool(request.SketchyClimb()),
447 SolidClimb: bool(request.SolidClimb()),
448 GoodDefense: bool(request.GoodDefense()),
449 BadDefense: bool(request.BadDefense()),
450 })
Alex Perry81f96ba2022-03-13 18:26:19 -0700451 if err != nil {
452 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert notes: %v", err))
453 return
454 }
455
456 var response SubmitNotesResponseT
457 builder := flatbuffers.NewBuilder(10)
458 builder.Finish((&response).Pack(builder))
459 w.Write(builder.FinishedBytes())
460}
461
Alex Perry81f96ba2022-03-13 18:26:19 -0700462type requestNotesForTeamHandler struct {
463 db Database
464}
465
466func (handler requestNotesForTeamHandler) 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, "RequestNotesForTeam", request_notes_for_team.GetRootAsRequestNotesForTeam)
Alex Perry81f96ba2022-03-13 18:26:19 -0700474 if !success {
475 return
476 }
477
Philipp Schradereecb8962022-06-01 21:02:42 -0700478 notes, err := handler.db.QueryNotes(request.Team())
Alex Perry81f96ba2022-03-13 18:26:19 -0700479 if err != nil {
480 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query notes: %v", err))
481 return
482 }
483
484 var response RequestNotesForTeamResponseT
Philipp Schradereecb8962022-06-01 21:02:42 -0700485 for _, data := range notes {
Alex Perry81f96ba2022-03-13 18:26:19 -0700486 response.Notes = append(response.Notes, &request_notes_for_team_response.NoteT{data})
487 }
488
489 builder := flatbuffers.NewBuilder(1024)
490 builder.Finish((&response).Pack(builder))
491 w.Write(builder.FinishedBytes())
492}
493
Milo Lin1d59f0c2022-06-22 20:30:58 -0700494type requestShiftScheduleHandler struct {
495 db Database
496}
497
498func (handler requestShiftScheduleHandler) 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
505 _, success := parseRequest(w, requestBytes, "RequestShiftSchedule", request_shift_schedule.GetRootAsRequestShiftSchedule)
506 if !success {
507 return
508 }
509
510 shiftData, err := handler.db.ReturnAllShifts()
511 if err != nil {
512 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query shift schedule: %v", err))
513 return
514 }
515
516 var response RequestShiftScheduleResponseT
517 for _, shifts := range shiftData {
518 response.ShiftSchedule = append(response.ShiftSchedule, &request_shift_schedule_response.MatchAssignmentT{
519 MatchNumber: shifts.MatchNumber,
520 R1scouter: shifts.R1scouter,
521 R2scouter: shifts.R2scouter,
522 R3scouter: shifts.R3scouter,
523 B1scouter: shifts.B1scouter,
524 B2scouter: shifts.B2scouter,
525 B3scouter: shifts.B3scouter,
526 })
527 }
528
529 builder := flatbuffers.NewBuilder(1024)
530 builder.Finish((&response).Pack(builder))
531 w.Write(builder.FinishedBytes())
532}
533
534type submitShiftScheduleHandler struct {
535 db Database
536}
537
538func (handler submitShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
539 // Get the username of the person submitting the data.
540 username := parseUsername(req)
541
542 requestBytes, err := io.ReadAll(req.Body)
543 if err != nil {
544 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
545 return
546 }
547
548 request, success := parseRequest[SubmitShiftSchedule](w, requestBytes, "SubmitShiftSchedule", submit_shift_schedule.GetRootAsSubmitShiftSchedule)
549 if !success {
550 return
551 }
552
553 log.Println("Got shift schedule from", username)
554 shift_schedule_length := request.ShiftScheduleLength()
555 for i := 0; i < shift_schedule_length; i++ {
556 var match_assignment submit_shift_schedule.MatchAssignment
557 request.ShiftSchedule(&match_assignment, i)
558 current_shift := db.Shift{
559 MatchNumber: match_assignment.MatchNumber(),
560 R1scouter: string(match_assignment.R1scouter()),
561 R2scouter: string(match_assignment.R2scouter()),
562 R3scouter: string(match_assignment.R3scouter()),
563 B1scouter: string(match_assignment.B1scouter()),
564 B2scouter: string(match_assignment.B2scouter()),
565 B3scouter: string(match_assignment.B3scouter()),
566 }
567 err = handler.db.AddToShift(current_shift)
568 if err != nil {
569 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit shift schedule: ", err))
570 return
571 }
572 }
573
574 builder := flatbuffers.NewBuilder(50 * 1024)
575 builder.Finish((&SubmitShiftScheduleResponseT{}).Pack(builder))
576 w.Write(builder.FinishedBytes())
577}
578
Filip Kujawa210a03b2022-11-24 14:41:11 -0800579type SubmitDriverRankingHandler struct {
580 db Database
581}
582
583func (handler SubmitDriverRankingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
584 requestBytes, err := io.ReadAll(req.Body)
585 if err != nil {
586 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
587 return
588 }
589
590 request, success := parseRequest(w, requestBytes, "SubmitDriverRanking", submit_driver_ranking.GetRootAsSubmitDriverRanking)
591 if !success {
592 return
593 }
594
595 err = handler.db.AddDriverRanking(db.DriverRankingData{
596 MatchNumber: request.MatchNumber(),
597 Rank1: request.Rank1(),
598 Rank2: request.Rank2(),
599 Rank3: request.Rank3(),
600 })
601
602 if err != nil {
603 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert driver ranking: %v", err))
604 return
605 }
606
607 var response SubmitDriverRankingResponseT
608 builder := flatbuffers.NewBuilder(10)
609 builder.Finish((&response).Pack(builder))
610 w.Write(builder.FinishedBytes())
611}
612
Filip Kujawaf882e022022-12-14 13:14:08 -0800613type requestAllNotesHandler struct {
614 db Database
615}
616
617func (handler requestAllNotesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
618 requestBytes, err := io.ReadAll(req.Body)
619 if err != nil {
620 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
621 return
622 }
623
624 _, success := parseRequest(w, requestBytes, "RequestAllNotes", request_all_notes.GetRootAsRequestAllNotes)
625 if !success {
626 return
627 }
628
629 notes, err := handler.db.ReturnAllNotes()
630 if err != nil {
631 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
632 return
633 }
634
635 var response RequestAllNotesResponseT
636 for _, note := range notes {
637 response.NoteList = append(response.NoteList, &request_all_notes_response.NoteT{
638 Team: note.TeamNumber,
639 Notes: note.Notes,
640 GoodDriving: note.GoodDriving,
641 BadDriving: note.BadDriving,
642 SketchyClimb: note.SketchyClimb,
643 SolidClimb: note.SolidClimb,
644 GoodDefense: note.GoodDefense,
645 BadDefense: note.BadDefense,
646 })
647 }
648
649 builder := flatbuffers.NewBuilder(50 * 1024)
650 builder.Finish((&response).Pack(builder))
651 w.Write(builder.FinishedBytes())
652}
653
654type requestAllDriverRankingsHandler struct {
655 db Database
656}
657
658func (handler requestAllDriverRankingsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
659 requestBytes, err := io.ReadAll(req.Body)
660 if err != nil {
661 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
662 return
663 }
664
665 _, success := parseRequest(w, requestBytes, "RequestAllDriverRankings", request_all_driver_rankings.GetRootAsRequestAllDriverRankings)
666 if !success {
667 return
668 }
669
670 rankings, err := handler.db.ReturnAllDriverRankings()
671 if err != nil {
672 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
673 return
674 }
675
676 var response RequestAllDriverRankingsResponseT
677 for _, ranking := range rankings {
678 response.DriverRankingList = append(response.DriverRankingList, &request_all_driver_rankings_response.RankingT{
679 MatchNumber: ranking.MatchNumber,
680 Rank1: ranking.Rank1,
681 Rank2: ranking.Rank2,
682 Rank3: ranking.Rank3,
683 })
684 }
685
686 builder := flatbuffers.NewBuilder(50 * 1024)
687 builder.Finish((&response).Pack(builder))
688 w.Write(builder.FinishedBytes())
689}
690
Philipp Schraderd3fac192022-03-02 20:35:46 -0800691func HandleRequests(db Database, scrape ScrapeMatchList, scoutingServer server.ScoutingServer) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800692 scoutingServer.HandleFunc("/requests", unknown)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800693 scoutingServer.Handle("/requests/submit/data_scouting", submitDataScoutingHandler{db})
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800694 scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
Filip Kujawaf882e022022-12-14 13:14:08 -0800695 scoutingServer.Handle("/requests/request/all_notes", requestAllNotesHandler{db})
696 scoutingServer.Handle("/requests/request/all_driver_rankings", requestAllDriverRankingsHandler{db})
Philipp Schraderacf96232022-03-01 22:03:30 -0800697 scoutingServer.Handle("/requests/request/data_scouting", requestDataScoutingHandler{db})
Philipp Schraderd3fac192022-03-02 20:35:46 -0800698 scoutingServer.Handle("/requests/refresh_match_list", refreshMatchListHandler{db, scrape})
Alex Perry81f96ba2022-03-13 18:26:19 -0700699 scoutingServer.Handle("/requests/submit/submit_notes", submitNoteScoutingHandler{db})
700 scoutingServer.Handle("/requests/request/notes_for_team", requestNotesForTeamHandler{db})
Milo Lin1d59f0c2022-06-22 20:30:58 -0700701 scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
702 scoutingServer.Handle("/requests/request/shift_schedule", requestShiftScheduleHandler{db})
Filip Kujawa210a03b2022-11-24 14:41:11 -0800703 scoutingServer.Handle("/requests/submit/submit_driver_ranking", SubmitDriverRankingHandler{db})
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800704}