blob: 76a26de0e0d6e0bc940f4f3f333d2d4ec766be39 [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)
Milo Lin1d59f0c2022-06-22 20:30:58 -070076 QueryAllShifts(int) ([]db.Shift, error)
Philipp Schrader8747f1b2022-02-23 23:56:22 -080077 QueryStats(int) ([]db.Stats, error)
Philipp Schradereecb8962022-06-01 21:02:42 -070078 QueryNotes(int32) ([]string, error)
Filip Kujawaf947cb42022-11-21 10:00:30 -080079 AddNotes(db.NotesData) error
Filip Kujawa210a03b2022-11-24 14:41:11 -080080 AddDriverRanking(db.DriverRankingData) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080081}
82
Philipp Schraderd3fac192022-03-02 20:35:46 -080083type ScrapeMatchList func(int32, string) ([]scraping.Match, error)
84
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080085// Handles unknown requests. Just returns a 404.
86func unknown(w http.ResponseWriter, req *http.Request) {
87 w.WriteHeader(http.StatusNotFound)
88}
89
90func respondWithError(w http.ResponseWriter, statusCode int, errorMessage string) {
91 builder := flatbuffers.NewBuilder(1024)
92 builder.Finish((&error_response.ErrorResponseT{
93 ErrorMessage: errorMessage,
94 }).Pack(builder))
95 w.WriteHeader(statusCode)
96 w.Write(builder.FinishedBytes())
97}
98
99func respondNotImplemented(w http.ResponseWriter) {
100 respondWithError(w, http.StatusNotImplemented, "")
101}
102
Philipp Schraderb7e75932022-03-26 16:18:34 -0700103func 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 -0800104 success := true
105 defer func() {
106 if r := recover(); r != nil {
Philipp Schraderb7e75932022-03-26 16:18:34 -0700107 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse %s: %v", requestName, r))
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800108 success = false
109 }
110 }()
Philipp Schraderb7e75932022-03-26 16:18:34 -0700111 result := parser(buf, 0)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800112 return result, success
113}
114
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700115// Parses the authorization information that the browser inserts into the
116// headers. The authorization follows this format:
117//
118// req.Headers["Authorization"] = []string{"Basic <base64 encoded username:password>"}
119func parseUsername(req *http.Request) string {
120 auth, ok := req.Header["Authorization"]
121 if !ok {
122 return "unknown"
123 }
124
125 parts := strings.Split(auth[0], " ")
126 if !(len(parts) == 2 && parts[0] == "Basic") {
127 return "unknown"
128 }
129
130 info, err := base64.StdEncoding.DecodeString(parts[1])
131 if err != nil {
132 log.Println("ERROR: Failed to parse Basic authentication.")
133 return "unknown"
134 }
135
136 loginParts := strings.Split(string(info), ":")
137 if len(loginParts) != 2 {
138 return "unknown"
139 }
140 return loginParts[0]
141}
142
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800143// Handles a SubmitDataScouting request.
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800144type submitDataScoutingHandler struct {
145 db Database
146}
147
148func (handler submitDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700149 // Get the username of the person submitting the data.
150 username := parseUsername(req)
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700151
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800152 requestBytes, err := io.ReadAll(req.Body)
153 if err != nil {
154 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
155 return
156 }
157
Philipp Schraderb7e75932022-03-26 16:18:34 -0700158 request, success := parseRequest[SubmitDataScouting](w, requestBytes, "SubmitDataScouting", submit_data_scouting.GetRootAsSubmitDataScouting)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800159 if !success {
160 return
161 }
162
Philipp Schraderd7b6eba2022-03-18 22:21:25 -0700163 log.Println("Got data scouting data for match", request.Match(), "team", request.Team(), "from", username)
164
Philipp Schrader30005e42022-03-06 13:53:58 -0800165 stats := db.Stats{
Philipp Schraderfee07e12022-03-17 22:19:47 -0700166 TeamNumber: request.Team(),
167 MatchNumber: request.Match(),
Philipp Schrader30b4a682022-04-16 14:36:17 -0700168 SetNumber: request.SetNumber(),
Philipp Schrader4535b7e2022-04-08 20:27:00 -0700169 CompLevel: string(request.CompLevel()),
Philipp Schraderfee07e12022-03-17 22:19:47 -0700170 StartingQuadrant: request.StartingQuadrant(),
171 AutoBallPickedUp: [5]bool{
172 request.AutoBall1(), request.AutoBall2(), request.AutoBall3(),
173 request.AutoBall4(), request.AutoBall5(),
174 },
Philipp Schraderfa45d742022-03-18 19:29:05 -0700175 ShotsMissedAuto: request.MissedShotsAuto(),
176 UpperGoalAuto: request.UpperGoalAuto(),
177 LowerGoalAuto: request.LowerGoalAuto(),
178 ShotsMissed: request.MissedShotsTele(),
179 UpperGoalShots: request.UpperGoalTele(),
180 LowerGoalShots: request.LowerGoalTele(),
181 PlayedDefense: request.DefenseRating(),
182 DefenseReceivedScore: request.DefenseReceivedRating(),
183 Climbing: int32(request.ClimbLevel()),
184 CollectedBy: username,
185 Comment: string(request.Comment()),
Philipp Schrader30005e42022-03-06 13:53:58 -0800186 }
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800187
Philipp Schraderfee07e12022-03-17 22:19:47 -0700188 // Do some error checking.
189 if stats.StartingQuadrant < 1 || stats.StartingQuadrant > 4 {
190 respondWithError(w, http.StatusBadRequest, fmt.Sprint(
191 "Invalid starting_quadrant field value of ", stats.StartingQuadrant))
192 return
193 }
194
Philipp Schrader30005e42022-03-06 13:53:58 -0800195 err = handler.db.AddToStats(stats)
196 if err != nil {
197 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit datascouting data: ", err))
Philipp Schraderfee07e12022-03-17 22:19:47 -0700198 return
Philipp Schrader30005e42022-03-06 13:53:58 -0800199 }
200
201 builder := flatbuffers.NewBuilder(50 * 1024)
202 builder.Finish((&SubmitDataScoutingResponseT{}).Pack(builder))
203 w.Write(builder.FinishedBytes())
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800204}
205
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800206// Handles a RequestAllMaches request.
207type requestAllMatchesHandler struct {
208 db Database
209}
210
211func (handler requestAllMatchesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
212 requestBytes, err := io.ReadAll(req.Body)
213 if err != nil {
214 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
215 return
216 }
217
Philipp Schraderb7e75932022-03-26 16:18:34 -0700218 _, success := parseRequest(w, requestBytes, "RequestAllMatches", request_all_matches.GetRootAsRequestAllMatches)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800219 if !success {
220 return
221 }
222
223 matches, err := handler.db.ReturnMatches()
224 if err != nil {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700225 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800226 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800227 }
228
229 var response RequestAllMatchesResponseT
230 for _, match := range matches {
231 response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
232 MatchNumber: match.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700233 SetNumber: match.SetNumber,
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800234 CompLevel: match.CompLevel,
235 R1: match.R1,
236 R2: match.R2,
237 R3: match.R3,
238 B1: match.B1,
239 B2: match.B2,
240 B3: match.B3,
241 })
242 }
243
244 builder := flatbuffers.NewBuilder(50 * 1024)
245 builder.Finish((&response).Pack(builder))
246 w.Write(builder.FinishedBytes())
247}
248
Philipp Schraderacf96232022-03-01 22:03:30 -0800249// Handles a RequestDataScouting request.
250type requestDataScoutingHandler struct {
251 db Database
252}
253
254func (handler requestDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
255 requestBytes, err := io.ReadAll(req.Body)
256 if err != nil {
257 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
258 return
259 }
260
Philipp Schraderb7e75932022-03-26 16:18:34 -0700261 _, success := parseRequest(w, requestBytes, "RequestDataScouting", request_data_scouting.GetRootAsRequestDataScouting)
Philipp Schraderacf96232022-03-01 22:03:30 -0800262 if !success {
263 return
264 }
265
266 stats, err := handler.db.ReturnStats()
267 if err != nil {
268 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800269 return
Philipp Schraderacf96232022-03-01 22:03:30 -0800270 }
271
272 var response RequestDataScoutingResponseT
273 for _, stat := range stats {
274 response.StatsList = append(response.StatsList, &request_data_scouting_response.StatsT{
Philipp Schraderfa45d742022-03-18 19:29:05 -0700275 Team: stat.TeamNumber,
276 Match: stat.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700277 SetNumber: stat.SetNumber,
Philipp Schrader4535b7e2022-04-08 20:27:00 -0700278 CompLevel: stat.CompLevel,
Philipp Schraderfa45d742022-03-18 19:29:05 -0700279 StartingQuadrant: stat.StartingQuadrant,
280 AutoBall1: stat.AutoBallPickedUp[0],
281 AutoBall2: stat.AutoBallPickedUp[1],
282 AutoBall3: stat.AutoBallPickedUp[2],
283 AutoBall4: stat.AutoBallPickedUp[3],
284 AutoBall5: stat.AutoBallPickedUp[4],
285 MissedShotsAuto: stat.ShotsMissedAuto,
286 UpperGoalAuto: stat.UpperGoalAuto,
287 LowerGoalAuto: stat.LowerGoalAuto,
288 MissedShotsTele: stat.ShotsMissed,
289 UpperGoalTele: stat.UpperGoalShots,
290 LowerGoalTele: stat.LowerGoalShots,
291 DefenseRating: stat.PlayedDefense,
292 DefenseReceivedRating: stat.DefenseReceivedScore,
293 ClimbLevel: request_data_scouting_response.ClimbLevel(stat.Climbing),
294 CollectedBy: stat.CollectedBy,
295 Comment: stat.Comment,
Philipp Schraderacf96232022-03-01 22:03:30 -0800296 })
297 }
298
299 builder := flatbuffers.NewBuilder(50 * 1024)
300 builder.Finish((&response).Pack(builder))
301 w.Write(builder.FinishedBytes())
302}
303
Philipp Schraderd3fac192022-03-02 20:35:46 -0800304func parseTeamKey(teamKey string) (int, error) {
305 // TBA prefixes teams with "frc". Not sure why. Get rid of that.
306 teamKey = strings.TrimPrefix(teamKey, "frc")
Philipp Schrader84fe7782022-11-12 08:10:36 -0800307 magnitude := 0
308 if strings.HasSuffix(teamKey, "A") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800309 magnitude = 0
Philipp Schrader84fe7782022-11-12 08:10:36 -0800310 teamKey = strings.TrimSuffix(teamKey, "A")
311 } else if strings.HasSuffix(teamKey, "B") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800312 magnitude = 9
Philipp Schrader84fe7782022-11-12 08:10:36 -0800313 teamKey = strings.TrimSuffix(teamKey, "B")
314 } else if strings.HasSuffix(teamKey, "C") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800315 magnitude = 8
Philipp Schrader84fe7782022-11-12 08:10:36 -0800316 teamKey = strings.TrimSuffix(teamKey, "C")
317 } else if strings.HasSuffix(teamKey, "D") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800318 magnitude = 7
Philipp Schrader84fe7782022-11-12 08:10:36 -0800319 teamKey = strings.TrimSuffix(teamKey, "D")
320 } else if strings.HasSuffix(teamKey, "E") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800321 magnitude = 6
Philipp Schrader84fe7782022-11-12 08:10:36 -0800322 teamKey = strings.TrimSuffix(teamKey, "E")
323 } else if strings.HasSuffix(teamKey, "F") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800324 magnitude = 5
Philipp Schrader84fe7782022-11-12 08:10:36 -0800325 teamKey = strings.TrimSuffix(teamKey, "F")
326 }
Filip Kujawa4de6feb2022-11-12 08:41:49 -0800327
328 if magnitude != 0 {
329 teamKey = strconv.Itoa(magnitude) + teamKey
Philipp Schrader84fe7782022-11-12 08:10:36 -0800330 }
Filip Kujawa4de6feb2022-11-12 08:41:49 -0800331
332 result, err := strconv.Atoi(teamKey)
Philipp Schrader84fe7782022-11-12 08:10:36 -0800333 return result, err
Philipp Schraderd3fac192022-03-02 20:35:46 -0800334}
335
336// Parses the alliance data from the specified match and returns the three red
337// teams and the three blue teams.
338func parseTeamKeys(match *scraping.Match) ([3]int32, [3]int32, error) {
339 redKeys := match.Alliances.Red.TeamKeys
340 blueKeys := match.Alliances.Blue.TeamKeys
341
342 if len(redKeys) != 3 || len(blueKeys) != 3 {
343 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
344 "Found %d red teams and %d blue teams.", len(redKeys), len(blueKeys)))
345 }
346
347 var red [3]int32
348 for i, key := range redKeys {
349 team, err := parseTeamKey(key)
350 if err != nil {
351 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
352 "Failed to parse red %d team '%s' as integer: %v", i+1, key, err))
353 }
354 red[i] = int32(team)
355 }
356 var blue [3]int32
357 for i, key := range blueKeys {
358 team, err := parseTeamKey(key)
359 if err != nil {
360 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
361 "Failed to parse blue %d team '%s' as integer: %v", i+1, key, err))
362 }
363 blue[i] = int32(team)
364 }
365 return red, blue, nil
366}
367
368type refreshMatchListHandler struct {
369 db Database
370 scrape ScrapeMatchList
371}
372
373func (handler refreshMatchListHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
374 requestBytes, err := io.ReadAll(req.Body)
375 if err != nil {
376 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
377 return
378 }
379
Philipp Schraderb7e75932022-03-26 16:18:34 -0700380 request, success := parseRequest(w, requestBytes, "RefreshMatchList", refresh_match_list.GetRootAsRefreshMatchList)
Philipp Schraderd3fac192022-03-02 20:35:46 -0800381 if !success {
382 return
383 }
384
385 matches, err := handler.scrape(request.Year(), string(request.EventCode()))
386 if err != nil {
387 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to scrape match list: ", err))
388 return
389 }
390
391 for _, match := range matches {
392 // Make sure the data is valid.
393 red, blue, err := parseTeamKeys(&match)
394 if err != nil {
395 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
396 "TheBlueAlliance data for match %d is malformed: %v", match.MatchNumber, err))
397 return
398 }
399 // Add the match to the database.
Philipp Schrader7365d322022-03-06 16:40:08 -0800400 err = handler.db.AddToMatch(db.Match{
Philipp Schraderd3fac192022-03-02 20:35:46 -0800401 MatchNumber: int32(match.MatchNumber),
Philipp Schrader30b4a682022-04-16 14:36:17 -0700402 SetNumber: int32(match.SetNumber),
Philipp Schrader45befdd2022-04-08 19:12:44 -0700403 CompLevel: match.CompLevel,
404 R1: red[0],
405 R2: red[1],
406 R3: red[2],
407 B1: blue[0],
408 B2: blue[1],
409 B3: blue[2],
Philipp Schraderd3fac192022-03-02 20:35:46 -0800410 })
Philipp Schrader7365d322022-03-06 16:40:08 -0800411 if err != nil {
412 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
413 "Failed to add match %d to the database: %v", match.MatchNumber, err))
414 return
415 }
Philipp Schraderd3fac192022-03-02 20:35:46 -0800416 }
417
418 var response RefreshMatchListResponseT
419 builder := flatbuffers.NewBuilder(1024)
420 builder.Finish((&response).Pack(builder))
421 w.Write(builder.FinishedBytes())
422}
423
Alex Perry81f96ba2022-03-13 18:26:19 -0700424type submitNoteScoutingHandler struct {
425 db Database
426}
427
428func (handler submitNoteScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
429 requestBytes, err := io.ReadAll(req.Body)
430 if err != nil {
431 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
432 return
433 }
434
Philipp Schraderb7e75932022-03-26 16:18:34 -0700435 request, success := parseRequest(w, requestBytes, "SubmitNotes", submit_notes.GetRootAsSubmitNotes)
Alex Perry81f96ba2022-03-13 18:26:19 -0700436 if !success {
437 return
438 }
439
Filip Kujawaf947cb42022-11-21 10:00:30 -0800440 err = handler.db.AddNotes(db.NotesData{
441 TeamNumber: request.Team(),
442 Notes: string(request.Notes()),
443 GoodDriving: bool(request.GoodDriving()),
444 BadDriving: bool(request.BadDriving()),
445 SketchyClimb: bool(request.SketchyClimb()),
446 SolidClimb: bool(request.SolidClimb()),
447 GoodDefense: bool(request.GoodDefense()),
448 BadDefense: bool(request.BadDefense()),
449 })
Alex Perry81f96ba2022-03-13 18:26:19 -0700450 if err != nil {
451 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert notes: %v", err))
452 return
453 }
454
455 var response SubmitNotesResponseT
456 builder := flatbuffers.NewBuilder(10)
457 builder.Finish((&response).Pack(builder))
458 w.Write(builder.FinishedBytes())
459}
460
Alex Perry81f96ba2022-03-13 18:26:19 -0700461type requestNotesForTeamHandler struct {
462 db Database
463}
464
465func (handler requestNotesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
466 requestBytes, err := io.ReadAll(req.Body)
467 if err != nil {
468 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
469 return
470 }
471
Philipp Schraderb7e75932022-03-26 16:18:34 -0700472 request, success := parseRequest(w, requestBytes, "RequestNotesForTeam", request_notes_for_team.GetRootAsRequestNotesForTeam)
Alex Perry81f96ba2022-03-13 18:26:19 -0700473 if !success {
474 return
475 }
476
Philipp Schradereecb8962022-06-01 21:02:42 -0700477 notes, err := handler.db.QueryNotes(request.Team())
Alex Perry81f96ba2022-03-13 18:26:19 -0700478 if err != nil {
479 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query notes: %v", err))
480 return
481 }
482
483 var response RequestNotesForTeamResponseT
Philipp Schradereecb8962022-06-01 21:02:42 -0700484 for _, data := range notes {
Alex Perry81f96ba2022-03-13 18:26:19 -0700485 response.Notes = append(response.Notes, &request_notes_for_team_response.NoteT{data})
486 }
487
488 builder := flatbuffers.NewBuilder(1024)
489 builder.Finish((&response).Pack(builder))
490 w.Write(builder.FinishedBytes())
491}
492
Milo Lin1d59f0c2022-06-22 20:30:58 -0700493type requestShiftScheduleHandler struct {
494 db Database
495}
496
497func (handler requestShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
498 requestBytes, err := io.ReadAll(req.Body)
499 if err != nil {
500 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
501 return
502 }
503
504 _, success := parseRequest(w, requestBytes, "RequestShiftSchedule", request_shift_schedule.GetRootAsRequestShiftSchedule)
505 if !success {
506 return
507 }
508
509 shiftData, err := handler.db.ReturnAllShifts()
510 if err != nil {
511 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query shift schedule: %v", err))
512 return
513 }
514
515 var response RequestShiftScheduleResponseT
516 for _, shifts := range shiftData {
517 response.ShiftSchedule = append(response.ShiftSchedule, &request_shift_schedule_response.MatchAssignmentT{
518 MatchNumber: shifts.MatchNumber,
519 R1scouter: shifts.R1scouter,
520 R2scouter: shifts.R2scouter,
521 R3scouter: shifts.R3scouter,
522 B1scouter: shifts.B1scouter,
523 B2scouter: shifts.B2scouter,
524 B3scouter: shifts.B3scouter,
525 })
526 }
527
528 builder := flatbuffers.NewBuilder(1024)
529 builder.Finish((&response).Pack(builder))
530 w.Write(builder.FinishedBytes())
531}
532
533type submitShiftScheduleHandler struct {
534 db Database
535}
536
537func (handler submitShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
538 // Get the username of the person submitting the data.
539 username := parseUsername(req)
540
541 requestBytes, err := io.ReadAll(req.Body)
542 if err != nil {
543 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
544 return
545 }
546
547 request, success := parseRequest[SubmitShiftSchedule](w, requestBytes, "SubmitShiftSchedule", submit_shift_schedule.GetRootAsSubmitShiftSchedule)
548 if !success {
549 return
550 }
551
552 log.Println("Got shift schedule from", username)
553 shift_schedule_length := request.ShiftScheduleLength()
554 for i := 0; i < shift_schedule_length; i++ {
555 var match_assignment submit_shift_schedule.MatchAssignment
556 request.ShiftSchedule(&match_assignment, i)
557 current_shift := db.Shift{
558 MatchNumber: match_assignment.MatchNumber(),
559 R1scouter: string(match_assignment.R1scouter()),
560 R2scouter: string(match_assignment.R2scouter()),
561 R3scouter: string(match_assignment.R3scouter()),
562 B1scouter: string(match_assignment.B1scouter()),
563 B2scouter: string(match_assignment.B2scouter()),
564 B3scouter: string(match_assignment.B3scouter()),
565 }
566 err = handler.db.AddToShift(current_shift)
567 if err != nil {
568 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit shift schedule: ", err))
569 return
570 }
571 }
572
573 builder := flatbuffers.NewBuilder(50 * 1024)
574 builder.Finish((&SubmitShiftScheduleResponseT{}).Pack(builder))
575 w.Write(builder.FinishedBytes())
576}
577
Filip Kujawa210a03b2022-11-24 14:41:11 -0800578type SubmitDriverRankingHandler struct {
579 db Database
580}
581
582func (handler SubmitDriverRankingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
583 requestBytes, err := io.ReadAll(req.Body)
584 if err != nil {
585 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
586 return
587 }
588
589 request, success := parseRequest(w, requestBytes, "SubmitDriverRanking", submit_driver_ranking.GetRootAsSubmitDriverRanking)
590 if !success {
591 return
592 }
593
594 err = handler.db.AddDriverRanking(db.DriverRankingData{
595 MatchNumber: request.MatchNumber(),
596 Rank1: request.Rank1(),
597 Rank2: request.Rank2(),
598 Rank3: request.Rank3(),
599 })
600
601 if err != nil {
602 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert driver ranking: %v", err))
603 return
604 }
605
606 var response SubmitDriverRankingResponseT
607 builder := flatbuffers.NewBuilder(10)
608 builder.Finish((&response).Pack(builder))
609 w.Write(builder.FinishedBytes())
610}
611
Filip Kujawaf882e022022-12-14 13:14:08 -0800612type requestAllNotesHandler struct {
613 db Database
614}
615
616func (handler requestAllNotesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
617 requestBytes, err := io.ReadAll(req.Body)
618 if err != nil {
619 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
620 return
621 }
622
623 _, success := parseRequest(w, requestBytes, "RequestAllNotes", request_all_notes.GetRootAsRequestAllNotes)
624 if !success {
625 return
626 }
627
628 notes, err := handler.db.ReturnAllNotes()
629 if err != nil {
630 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
631 return
632 }
633
634 var response RequestAllNotesResponseT
635 for _, note := range notes {
636 response.NoteList = append(response.NoteList, &request_all_notes_response.NoteT{
637 Team: note.TeamNumber,
638 Notes: note.Notes,
639 GoodDriving: note.GoodDriving,
640 BadDriving: note.BadDriving,
641 SketchyClimb: note.SketchyClimb,
642 SolidClimb: note.SolidClimb,
643 GoodDefense: note.GoodDefense,
644 BadDefense: note.BadDefense,
645 })
646 }
647
648 builder := flatbuffers.NewBuilder(50 * 1024)
649 builder.Finish((&response).Pack(builder))
650 w.Write(builder.FinishedBytes())
651}
652
653type requestAllDriverRankingsHandler struct {
654 db Database
655}
656
657func (handler requestAllDriverRankingsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
658 requestBytes, err := io.ReadAll(req.Body)
659 if err != nil {
660 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
661 return
662 }
663
664 _, success := parseRequest(w, requestBytes, "RequestAllDriverRankings", request_all_driver_rankings.GetRootAsRequestAllDriverRankings)
665 if !success {
666 return
667 }
668
669 rankings, err := handler.db.ReturnAllDriverRankings()
670 if err != nil {
671 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
672 return
673 }
674
675 var response RequestAllDriverRankingsResponseT
676 for _, ranking := range rankings {
677 response.DriverRankingList = append(response.DriverRankingList, &request_all_driver_rankings_response.RankingT{
678 MatchNumber: ranking.MatchNumber,
679 Rank1: ranking.Rank1,
680 Rank2: ranking.Rank2,
681 Rank3: ranking.Rank3,
682 })
683 }
684
685 builder := flatbuffers.NewBuilder(50 * 1024)
686 builder.Finish((&response).Pack(builder))
687 w.Write(builder.FinishedBytes())
688}
689
Philipp Schraderd3fac192022-03-02 20:35:46 -0800690func HandleRequests(db Database, scrape ScrapeMatchList, scoutingServer server.ScoutingServer) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800691 scoutingServer.HandleFunc("/requests", unknown)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800692 scoutingServer.Handle("/requests/submit/data_scouting", submitDataScoutingHandler{db})
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800693 scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
Filip Kujawaf882e022022-12-14 13:14:08 -0800694 scoutingServer.Handle("/requests/request/all_notes", requestAllNotesHandler{db})
695 scoutingServer.Handle("/requests/request/all_driver_rankings", requestAllDriverRankingsHandler{db})
Philipp Schraderacf96232022-03-01 22:03:30 -0800696 scoutingServer.Handle("/requests/request/data_scouting", requestDataScoutingHandler{db})
Philipp Schraderd3fac192022-03-02 20:35:46 -0800697 scoutingServer.Handle("/requests/refresh_match_list", refreshMatchListHandler{db, scrape})
Alex Perry81f96ba2022-03-13 18:26:19 -0700698 scoutingServer.Handle("/requests/submit/submit_notes", submitNoteScoutingHandler{db})
699 scoutingServer.Handle("/requests/request/notes_for_team", requestNotesForTeamHandler{db})
Milo Lin1d59f0c2022-06-22 20:30:58 -0700700 scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
701 scoutingServer.Handle("/requests/request/shift_schedule", requestShiftScheduleHandler{db})
Filip Kujawa210a03b2022-11-24 14:41:11 -0800702 scoutingServer.Handle("/requests/submit/submit_driver_ranking", SubmitDriverRankingHandler{db})
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800703}