blob: a7f1a7759da86fba1b163390089c4d8451d39672 [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"
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080026 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team"
27 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070028 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team"
29 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070030 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule"
31 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080032 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting"
Philipp Schrader30005e42022-03-06 13:53:58 -080033 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
Filip Kujawa210a03b2022-11-24 14:41:11 -080034 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking"
35 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070036 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes"
37 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070038 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule"
39 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080040 "github.com/frc971/971-Robot-Code/scouting/webserver/server"
41 flatbuffers "github.com/google/flatbuffers/go"
42)
43
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080044type SubmitDataScouting = submit_data_scouting.SubmitDataScouting
Philipp Schrader30005e42022-03-06 13:53:58 -080045type SubmitDataScoutingResponseT = submit_data_scouting_response.SubmitDataScoutingResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080046type RequestAllMatches = request_all_matches.RequestAllMatches
47type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
Filip Kujawaf882e022022-12-14 13:14:08 -080048type RequestAllDriverRankings = request_all_driver_rankings.RequestAllDriverRankings
49type RequestAllDriverRankingsResponseT = request_all_driver_rankings_response.RequestAllDriverRankingsResponseT
50type RequestAllNotes = request_all_notes.RequestAllNotes
51type RequestAllNotesResponseT = request_all_notes_response.RequestAllNotesResponseT
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080052type RequestMatchesForTeam = request_matches_for_team.RequestMatchesForTeam
53type RequestMatchesForTeamResponseT = request_matches_for_team_response.RequestMatchesForTeamResponseT
Philipp Schraderacf96232022-03-01 22:03:30 -080054type RequestDataScouting = request_data_scouting.RequestDataScouting
55type RequestDataScoutingResponseT = request_data_scouting_response.RequestDataScoutingResponseT
Philipp Schraderd3fac192022-03-02 20:35:46 -080056type RefreshMatchList = refresh_match_list.RefreshMatchList
57type RefreshMatchListResponseT = refresh_match_list_response.RefreshMatchListResponseT
Alex Perry81f96ba2022-03-13 18:26:19 -070058type SubmitNotes = submit_notes.SubmitNotes
59type SubmitNotesResponseT = submit_notes_response.SubmitNotesResponseT
60type RequestNotesForTeam = request_notes_for_team.RequestNotesForTeam
61type RequestNotesForTeamResponseT = request_notes_for_team_response.RequestNotesForTeamResponseT
Milo Lin1d59f0c2022-06-22 20:30:58 -070062type RequestShiftSchedule = request_shift_schedule.RequestShiftSchedule
63type RequestShiftScheduleResponseT = request_shift_schedule_response.RequestShiftScheduleResponseT
64type SubmitShiftSchedule = submit_shift_schedule.SubmitShiftSchedule
65type SubmitShiftScheduleResponseT = submit_shift_schedule_response.SubmitShiftScheduleResponseT
Filip Kujawa210a03b2022-11-24 14:41:11 -080066type SubmitDriverRanking = submit_driver_ranking.SubmitDriverRanking
67type SubmitDriverRankingResponseT = submit_driver_ranking_response.SubmitDriverRankingResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080068
Philipp Schrader8747f1b2022-02-23 23:56:22 -080069// The interface we expect the database abstraction to conform to.
70// We use an interface here because it makes unit testing easier.
71type Database interface {
72 AddToMatch(db.Match) error
Milo Lin1d59f0c2022-06-22 20:30:58 -070073 AddToShift(db.Shift) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080074 AddToStats(db.Stats) error
75 ReturnMatches() ([]db.Match, error)
Filip Kujawaf882e022022-12-14 13:14:08 -080076 ReturnAllNotes() ([]db.NotesData, error)
77 ReturnAllDriverRankings() ([]db.DriverRankingData, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070078 ReturnAllShifts() ([]db.Shift, error)
Philipp Schrader8747f1b2022-02-23 23:56:22 -080079 ReturnStats() ([]db.Stats, error)
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080080 QueryMatches(int32) ([]db.Match, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070081 QueryAllShifts(int) ([]db.Shift, error)
Philipp Schrader8747f1b2022-02-23 23:56:22 -080082 QueryStats(int) ([]db.Stats, error)
Philipp Schradereecb8962022-06-01 21:02:42 -070083 QueryNotes(int32) ([]string, error)
Filip Kujawaf947cb42022-11-21 10:00:30 -080084 AddNotes(db.NotesData) error
Filip Kujawa210a03b2022-11-24 14:41:11 -080085 AddDriverRanking(db.DriverRankingData) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080086}
87
Philipp Schraderd3fac192022-03-02 20:35:46 -080088type ScrapeMatchList func(int32, string) ([]scraping.Match, error)
89
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080090// Handles unknown requests. Just returns a 404.
91func unknown(w http.ResponseWriter, req *http.Request) {
92 w.WriteHeader(http.StatusNotFound)
93}
94
95func respondWithError(w http.ResponseWriter, statusCode int, errorMessage string) {
96 builder := flatbuffers.NewBuilder(1024)
97 builder.Finish((&error_response.ErrorResponseT{
98 ErrorMessage: errorMessage,
99 }).Pack(builder))
100 w.WriteHeader(statusCode)
101 w.Write(builder.FinishedBytes())
102}
103
104func respondNotImplemented(w http.ResponseWriter) {
105 respondWithError(w, http.StatusNotImplemented, "")
106}
107
Philipp Schraderb7e75932022-03-26 16:18:34 -0700108func 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 -0800109 success := true
110 defer func() {
111 if r := recover(); r != nil {
Philipp Schraderb7e75932022-03-26 16:18:34 -0700112 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse %s: %v", requestName, r))
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800113 success = false
114 }
115 }()
Philipp Schraderb7e75932022-03-26 16:18:34 -0700116 result := parser(buf, 0)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800117 return result, success
118}
119
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700120// Parses the authorization information that the browser inserts into the
121// headers. The authorization follows this format:
122//
123// req.Headers["Authorization"] = []string{"Basic <base64 encoded username:password>"}
124func parseUsername(req *http.Request) string {
125 auth, ok := req.Header["Authorization"]
126 if !ok {
127 return "unknown"
128 }
129
130 parts := strings.Split(auth[0], " ")
131 if !(len(parts) == 2 && parts[0] == "Basic") {
132 return "unknown"
133 }
134
135 info, err := base64.StdEncoding.DecodeString(parts[1])
136 if err != nil {
137 log.Println("ERROR: Failed to parse Basic authentication.")
138 return "unknown"
139 }
140
141 loginParts := strings.Split(string(info), ":")
142 if len(loginParts) != 2 {
143 return "unknown"
144 }
145 return loginParts[0]
146}
147
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800148// Handles a SubmitDataScouting request.
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800149type submitDataScoutingHandler struct {
150 db Database
151}
152
153func (handler submitDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700154 // Get the username of the person submitting the data.
155 username := parseUsername(req)
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700156
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800157 requestBytes, err := io.ReadAll(req.Body)
158 if err != nil {
159 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
160 return
161 }
162
Philipp Schraderb7e75932022-03-26 16:18:34 -0700163 request, success := parseRequest[SubmitDataScouting](w, requestBytes, "SubmitDataScouting", submit_data_scouting.GetRootAsSubmitDataScouting)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800164 if !success {
165 return
166 }
167
Philipp Schraderd7b6eba2022-03-18 22:21:25 -0700168 log.Println("Got data scouting data for match", request.Match(), "team", request.Team(), "from", username)
169
Philipp Schrader30005e42022-03-06 13:53:58 -0800170 stats := db.Stats{
Philipp Schraderfee07e12022-03-17 22:19:47 -0700171 TeamNumber: request.Team(),
172 MatchNumber: request.Match(),
Philipp Schrader30b4a682022-04-16 14:36:17 -0700173 SetNumber: request.SetNumber(),
Philipp Schrader4535b7e2022-04-08 20:27:00 -0700174 CompLevel: string(request.CompLevel()),
Philipp Schraderfee07e12022-03-17 22:19:47 -0700175 StartingQuadrant: request.StartingQuadrant(),
176 AutoBallPickedUp: [5]bool{
177 request.AutoBall1(), request.AutoBall2(), request.AutoBall3(),
178 request.AutoBall4(), request.AutoBall5(),
179 },
Philipp Schraderfa45d742022-03-18 19:29:05 -0700180 ShotsMissedAuto: request.MissedShotsAuto(),
181 UpperGoalAuto: request.UpperGoalAuto(),
182 LowerGoalAuto: request.LowerGoalAuto(),
183 ShotsMissed: request.MissedShotsTele(),
184 UpperGoalShots: request.UpperGoalTele(),
185 LowerGoalShots: request.LowerGoalTele(),
186 PlayedDefense: request.DefenseRating(),
187 DefenseReceivedScore: request.DefenseReceivedRating(),
188 Climbing: int32(request.ClimbLevel()),
189 CollectedBy: username,
190 Comment: string(request.Comment()),
Philipp Schrader30005e42022-03-06 13:53:58 -0800191 }
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800192
Philipp Schraderfee07e12022-03-17 22:19:47 -0700193 // Do some error checking.
194 if stats.StartingQuadrant < 1 || stats.StartingQuadrant > 4 {
195 respondWithError(w, http.StatusBadRequest, fmt.Sprint(
196 "Invalid starting_quadrant field value of ", stats.StartingQuadrant))
197 return
198 }
199
Philipp Schrader30005e42022-03-06 13:53:58 -0800200 err = handler.db.AddToStats(stats)
201 if err != nil {
202 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit datascouting data: ", err))
Philipp Schraderfee07e12022-03-17 22:19:47 -0700203 return
Philipp Schrader30005e42022-03-06 13:53:58 -0800204 }
205
206 builder := flatbuffers.NewBuilder(50 * 1024)
207 builder.Finish((&SubmitDataScoutingResponseT{}).Pack(builder))
208 w.Write(builder.FinishedBytes())
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800209}
210
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800211// Handles a RequestAllMaches request.
212type requestAllMatchesHandler struct {
213 db Database
214}
215
216func (handler requestAllMatchesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
217 requestBytes, err := io.ReadAll(req.Body)
218 if err != nil {
219 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
220 return
221 }
222
Philipp Schraderb7e75932022-03-26 16:18:34 -0700223 _, success := parseRequest(w, requestBytes, "RequestAllMatches", request_all_matches.GetRootAsRequestAllMatches)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800224 if !success {
225 return
226 }
227
228 matches, err := handler.db.ReturnMatches()
229 if err != nil {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700230 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800231 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800232 }
233
234 var response RequestAllMatchesResponseT
235 for _, match := range matches {
236 response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
237 MatchNumber: match.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700238 SetNumber: match.SetNumber,
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800239 CompLevel: match.CompLevel,
240 R1: match.R1,
241 R2: match.R2,
242 R3: match.R3,
243 B1: match.B1,
244 B2: match.B2,
245 B3: match.B3,
246 })
247 }
248
249 builder := flatbuffers.NewBuilder(50 * 1024)
250 builder.Finish((&response).Pack(builder))
251 w.Write(builder.FinishedBytes())
252}
253
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800254// Handles a RequestMatchesForTeam request.
255type requestMatchesForTeamHandler struct {
256 db Database
257}
258
259func (handler requestMatchesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
260 requestBytes, err := io.ReadAll(req.Body)
261 if err != nil {
262 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
263 return
264 }
265
Philipp Schraderb7e75932022-03-26 16:18:34 -0700266 request, success := parseRequest(w, requestBytes, "RequestMatchesForTeam", request_matches_for_team.GetRootAsRequestMatchesForTeam)
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800267 if !success {
268 return
269 }
270
271 matches, err := handler.db.QueryMatches(request.Team())
272 if err != nil {
273 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800274 return
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800275 }
276
277 var response RequestAllMatchesResponseT
278 for _, match := range matches {
279 response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
280 MatchNumber: match.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700281 SetNumber: match.SetNumber,
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800282 CompLevel: match.CompLevel,
283 R1: match.R1,
284 R2: match.R2,
285 R3: match.R3,
286 B1: match.B1,
287 B2: match.B2,
288 B3: match.B3,
289 })
290 }
291
292 builder := flatbuffers.NewBuilder(50 * 1024)
293 builder.Finish((&response).Pack(builder))
294 w.Write(builder.FinishedBytes())
295}
296
Philipp Schraderacf96232022-03-01 22:03:30 -0800297// Handles a RequestDataScouting request.
298type requestDataScoutingHandler struct {
299 db Database
300}
301
302func (handler requestDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
303 requestBytes, err := io.ReadAll(req.Body)
304 if err != nil {
305 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
306 return
307 }
308
Philipp Schraderb7e75932022-03-26 16:18:34 -0700309 _, success := parseRequest(w, requestBytes, "RequestDataScouting", request_data_scouting.GetRootAsRequestDataScouting)
Philipp Schraderacf96232022-03-01 22:03:30 -0800310 if !success {
311 return
312 }
313
314 stats, err := handler.db.ReturnStats()
315 if err != nil {
316 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800317 return
Philipp Schraderacf96232022-03-01 22:03:30 -0800318 }
319
320 var response RequestDataScoutingResponseT
321 for _, stat := range stats {
322 response.StatsList = append(response.StatsList, &request_data_scouting_response.StatsT{
Philipp Schraderfa45d742022-03-18 19:29:05 -0700323 Team: stat.TeamNumber,
324 Match: stat.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700325 SetNumber: stat.SetNumber,
Philipp Schrader4535b7e2022-04-08 20:27:00 -0700326 CompLevel: stat.CompLevel,
Philipp Schraderfa45d742022-03-18 19:29:05 -0700327 StartingQuadrant: stat.StartingQuadrant,
328 AutoBall1: stat.AutoBallPickedUp[0],
329 AutoBall2: stat.AutoBallPickedUp[1],
330 AutoBall3: stat.AutoBallPickedUp[2],
331 AutoBall4: stat.AutoBallPickedUp[3],
332 AutoBall5: stat.AutoBallPickedUp[4],
333 MissedShotsAuto: stat.ShotsMissedAuto,
334 UpperGoalAuto: stat.UpperGoalAuto,
335 LowerGoalAuto: stat.LowerGoalAuto,
336 MissedShotsTele: stat.ShotsMissed,
337 UpperGoalTele: stat.UpperGoalShots,
338 LowerGoalTele: stat.LowerGoalShots,
339 DefenseRating: stat.PlayedDefense,
340 DefenseReceivedRating: stat.DefenseReceivedScore,
341 ClimbLevel: request_data_scouting_response.ClimbLevel(stat.Climbing),
342 CollectedBy: stat.CollectedBy,
343 Comment: stat.Comment,
Philipp Schraderacf96232022-03-01 22:03:30 -0800344 })
345 }
346
347 builder := flatbuffers.NewBuilder(50 * 1024)
348 builder.Finish((&response).Pack(builder))
349 w.Write(builder.FinishedBytes())
350}
351
Philipp Schraderd3fac192022-03-02 20:35:46 -0800352func parseTeamKey(teamKey string) (int, error) {
353 // TBA prefixes teams with "frc". Not sure why. Get rid of that.
354 teamKey = strings.TrimPrefix(teamKey, "frc")
Philipp Schrader84fe7782022-11-12 08:10:36 -0800355 magnitude := 0
356 if strings.HasSuffix(teamKey, "A") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800357 magnitude = 0
Philipp Schrader84fe7782022-11-12 08:10:36 -0800358 teamKey = strings.TrimSuffix(teamKey, "A")
359 } else if strings.HasSuffix(teamKey, "B") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800360 magnitude = 9
Philipp Schrader84fe7782022-11-12 08:10:36 -0800361 teamKey = strings.TrimSuffix(teamKey, "B")
362 } else if strings.HasSuffix(teamKey, "C") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800363 magnitude = 8
Philipp Schrader84fe7782022-11-12 08:10:36 -0800364 teamKey = strings.TrimSuffix(teamKey, "C")
365 } else if strings.HasSuffix(teamKey, "D") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800366 magnitude = 7
Philipp Schrader84fe7782022-11-12 08:10:36 -0800367 teamKey = strings.TrimSuffix(teamKey, "D")
368 } else if strings.HasSuffix(teamKey, "E") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800369 magnitude = 6
Philipp Schrader84fe7782022-11-12 08:10:36 -0800370 teamKey = strings.TrimSuffix(teamKey, "E")
371 } else if strings.HasSuffix(teamKey, "F") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800372 magnitude = 5
Philipp Schrader84fe7782022-11-12 08:10:36 -0800373 teamKey = strings.TrimSuffix(teamKey, "F")
374 }
Filip Kujawa4de6feb2022-11-12 08:41:49 -0800375
376 if magnitude != 0 {
377 teamKey = strconv.Itoa(magnitude) + teamKey
Philipp Schrader84fe7782022-11-12 08:10:36 -0800378 }
Filip Kujawa4de6feb2022-11-12 08:41:49 -0800379
380 result, err := strconv.Atoi(teamKey)
Philipp Schrader84fe7782022-11-12 08:10:36 -0800381 return result, err
Philipp Schraderd3fac192022-03-02 20:35:46 -0800382}
383
384// Parses the alliance data from the specified match and returns the three red
385// teams and the three blue teams.
386func parseTeamKeys(match *scraping.Match) ([3]int32, [3]int32, error) {
387 redKeys := match.Alliances.Red.TeamKeys
388 blueKeys := match.Alliances.Blue.TeamKeys
389
390 if len(redKeys) != 3 || len(blueKeys) != 3 {
391 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
392 "Found %d red teams and %d blue teams.", len(redKeys), len(blueKeys)))
393 }
394
395 var red [3]int32
396 for i, key := range redKeys {
397 team, err := parseTeamKey(key)
398 if err != nil {
399 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
400 "Failed to parse red %d team '%s' as integer: %v", i+1, key, err))
401 }
402 red[i] = int32(team)
403 }
404 var blue [3]int32
405 for i, key := range blueKeys {
406 team, err := parseTeamKey(key)
407 if err != nil {
408 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
409 "Failed to parse blue %d team '%s' as integer: %v", i+1, key, err))
410 }
411 blue[i] = int32(team)
412 }
413 return red, blue, nil
414}
415
416type refreshMatchListHandler struct {
417 db Database
418 scrape ScrapeMatchList
419}
420
421func (handler refreshMatchListHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
422 requestBytes, err := io.ReadAll(req.Body)
423 if err != nil {
424 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
425 return
426 }
427
Philipp Schraderb7e75932022-03-26 16:18:34 -0700428 request, success := parseRequest(w, requestBytes, "RefreshMatchList", refresh_match_list.GetRootAsRefreshMatchList)
Philipp Schraderd3fac192022-03-02 20:35:46 -0800429 if !success {
430 return
431 }
432
433 matches, err := handler.scrape(request.Year(), string(request.EventCode()))
434 if err != nil {
435 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to scrape match list: ", err))
436 return
437 }
438
439 for _, match := range matches {
440 // Make sure the data is valid.
441 red, blue, err := parseTeamKeys(&match)
442 if err != nil {
443 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
444 "TheBlueAlliance data for match %d is malformed: %v", match.MatchNumber, err))
445 return
446 }
447 // Add the match to the database.
Philipp Schrader7365d322022-03-06 16:40:08 -0800448 err = handler.db.AddToMatch(db.Match{
Philipp Schraderd3fac192022-03-02 20:35:46 -0800449 MatchNumber: int32(match.MatchNumber),
Philipp Schrader30b4a682022-04-16 14:36:17 -0700450 SetNumber: int32(match.SetNumber),
Philipp Schrader45befdd2022-04-08 19:12:44 -0700451 CompLevel: match.CompLevel,
452 R1: red[0],
453 R2: red[1],
454 R3: red[2],
455 B1: blue[0],
456 B2: blue[1],
457 B3: blue[2],
Philipp Schraderd3fac192022-03-02 20:35:46 -0800458 })
Philipp Schrader7365d322022-03-06 16:40:08 -0800459 if err != nil {
460 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
461 "Failed to add match %d to the database: %v", match.MatchNumber, err))
462 return
463 }
Philipp Schraderd3fac192022-03-02 20:35:46 -0800464 }
465
466 var response RefreshMatchListResponseT
467 builder := flatbuffers.NewBuilder(1024)
468 builder.Finish((&response).Pack(builder))
469 w.Write(builder.FinishedBytes())
470}
471
Alex Perry81f96ba2022-03-13 18:26:19 -0700472type submitNoteScoutingHandler struct {
473 db Database
474}
475
476func (handler submitNoteScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
477 requestBytes, err := io.ReadAll(req.Body)
478 if err != nil {
479 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
480 return
481 }
482
Philipp Schraderb7e75932022-03-26 16:18:34 -0700483 request, success := parseRequest(w, requestBytes, "SubmitNotes", submit_notes.GetRootAsSubmitNotes)
Alex Perry81f96ba2022-03-13 18:26:19 -0700484 if !success {
485 return
486 }
487
Filip Kujawaf947cb42022-11-21 10:00:30 -0800488 err = handler.db.AddNotes(db.NotesData{
489 TeamNumber: request.Team(),
490 Notes: string(request.Notes()),
491 GoodDriving: bool(request.GoodDriving()),
492 BadDriving: bool(request.BadDriving()),
493 SketchyClimb: bool(request.SketchyClimb()),
494 SolidClimb: bool(request.SolidClimb()),
495 GoodDefense: bool(request.GoodDefense()),
496 BadDefense: bool(request.BadDefense()),
497 })
Alex Perry81f96ba2022-03-13 18:26:19 -0700498 if err != nil {
499 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert notes: %v", err))
500 return
501 }
502
503 var response SubmitNotesResponseT
504 builder := flatbuffers.NewBuilder(10)
505 builder.Finish((&response).Pack(builder))
506 w.Write(builder.FinishedBytes())
507}
508
Alex Perry81f96ba2022-03-13 18:26:19 -0700509type requestNotesForTeamHandler struct {
510 db Database
511}
512
513func (handler requestNotesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
514 requestBytes, err := io.ReadAll(req.Body)
515 if err != nil {
516 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
517 return
518 }
519
Philipp Schraderb7e75932022-03-26 16:18:34 -0700520 request, success := parseRequest(w, requestBytes, "RequestNotesForTeam", request_notes_for_team.GetRootAsRequestNotesForTeam)
Alex Perry81f96ba2022-03-13 18:26:19 -0700521 if !success {
522 return
523 }
524
Philipp Schradereecb8962022-06-01 21:02:42 -0700525 notes, err := handler.db.QueryNotes(request.Team())
Alex Perry81f96ba2022-03-13 18:26:19 -0700526 if err != nil {
527 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query notes: %v", err))
528 return
529 }
530
531 var response RequestNotesForTeamResponseT
Philipp Schradereecb8962022-06-01 21:02:42 -0700532 for _, data := range notes {
Alex Perry81f96ba2022-03-13 18:26:19 -0700533 response.Notes = append(response.Notes, &request_notes_for_team_response.NoteT{data})
534 }
535
536 builder := flatbuffers.NewBuilder(1024)
537 builder.Finish((&response).Pack(builder))
538 w.Write(builder.FinishedBytes())
539}
540
Milo Lin1d59f0c2022-06-22 20:30:58 -0700541type requestShiftScheduleHandler struct {
542 db Database
543}
544
545func (handler requestShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
546 requestBytes, err := io.ReadAll(req.Body)
547 if err != nil {
548 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
549 return
550 }
551
552 _, success := parseRequest(w, requestBytes, "RequestShiftSchedule", request_shift_schedule.GetRootAsRequestShiftSchedule)
553 if !success {
554 return
555 }
556
557 shiftData, err := handler.db.ReturnAllShifts()
558 if err != nil {
559 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query shift schedule: %v", err))
560 return
561 }
562
563 var response RequestShiftScheduleResponseT
564 for _, shifts := range shiftData {
565 response.ShiftSchedule = append(response.ShiftSchedule, &request_shift_schedule_response.MatchAssignmentT{
566 MatchNumber: shifts.MatchNumber,
567 R1scouter: shifts.R1scouter,
568 R2scouter: shifts.R2scouter,
569 R3scouter: shifts.R3scouter,
570 B1scouter: shifts.B1scouter,
571 B2scouter: shifts.B2scouter,
572 B3scouter: shifts.B3scouter,
573 })
574 }
575
576 builder := flatbuffers.NewBuilder(1024)
577 builder.Finish((&response).Pack(builder))
578 w.Write(builder.FinishedBytes())
579}
580
581type submitShiftScheduleHandler struct {
582 db Database
583}
584
585func (handler submitShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
586 // Get the username of the person submitting the data.
587 username := parseUsername(req)
588
589 requestBytes, err := io.ReadAll(req.Body)
590 if err != nil {
591 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
592 return
593 }
594
595 request, success := parseRequest[SubmitShiftSchedule](w, requestBytes, "SubmitShiftSchedule", submit_shift_schedule.GetRootAsSubmitShiftSchedule)
596 if !success {
597 return
598 }
599
600 log.Println("Got shift schedule from", username)
601 shift_schedule_length := request.ShiftScheduleLength()
602 for i := 0; i < shift_schedule_length; i++ {
603 var match_assignment submit_shift_schedule.MatchAssignment
604 request.ShiftSchedule(&match_assignment, i)
605 current_shift := db.Shift{
606 MatchNumber: match_assignment.MatchNumber(),
607 R1scouter: string(match_assignment.R1scouter()),
608 R2scouter: string(match_assignment.R2scouter()),
609 R3scouter: string(match_assignment.R3scouter()),
610 B1scouter: string(match_assignment.B1scouter()),
611 B2scouter: string(match_assignment.B2scouter()),
612 B3scouter: string(match_assignment.B3scouter()),
613 }
614 err = handler.db.AddToShift(current_shift)
615 if err != nil {
616 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit shift schedule: ", err))
617 return
618 }
619 }
620
621 builder := flatbuffers.NewBuilder(50 * 1024)
622 builder.Finish((&SubmitShiftScheduleResponseT{}).Pack(builder))
623 w.Write(builder.FinishedBytes())
624}
625
Filip Kujawa210a03b2022-11-24 14:41:11 -0800626type SubmitDriverRankingHandler struct {
627 db Database
628}
629
630func (handler SubmitDriverRankingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
631 requestBytes, err := io.ReadAll(req.Body)
632 if err != nil {
633 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
634 return
635 }
636
637 request, success := parseRequest(w, requestBytes, "SubmitDriverRanking", submit_driver_ranking.GetRootAsSubmitDriverRanking)
638 if !success {
639 return
640 }
641
642 err = handler.db.AddDriverRanking(db.DriverRankingData{
643 MatchNumber: request.MatchNumber(),
644 Rank1: request.Rank1(),
645 Rank2: request.Rank2(),
646 Rank3: request.Rank3(),
647 })
648
649 if err != nil {
650 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert driver ranking: %v", err))
651 return
652 }
653
654 var response SubmitDriverRankingResponseT
655 builder := flatbuffers.NewBuilder(10)
656 builder.Finish((&response).Pack(builder))
657 w.Write(builder.FinishedBytes())
658}
659
Filip Kujawaf882e022022-12-14 13:14:08 -0800660type requestAllNotesHandler struct {
661 db Database
662}
663
664func (handler requestAllNotesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
665 requestBytes, err := io.ReadAll(req.Body)
666 if err != nil {
667 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
668 return
669 }
670
671 _, success := parseRequest(w, requestBytes, "RequestAllNotes", request_all_notes.GetRootAsRequestAllNotes)
672 if !success {
673 return
674 }
675
676 notes, err := handler.db.ReturnAllNotes()
677 if err != nil {
678 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
679 return
680 }
681
682 var response RequestAllNotesResponseT
683 for _, note := range notes {
684 response.NoteList = append(response.NoteList, &request_all_notes_response.NoteT{
685 Team: note.TeamNumber,
686 Notes: note.Notes,
687 GoodDriving: note.GoodDriving,
688 BadDriving: note.BadDriving,
689 SketchyClimb: note.SketchyClimb,
690 SolidClimb: note.SolidClimb,
691 GoodDefense: note.GoodDefense,
692 BadDefense: note.BadDefense,
693 })
694 }
695
696 builder := flatbuffers.NewBuilder(50 * 1024)
697 builder.Finish((&response).Pack(builder))
698 w.Write(builder.FinishedBytes())
699}
700
701type requestAllDriverRankingsHandler struct {
702 db Database
703}
704
705func (handler requestAllDriverRankingsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
706 requestBytes, err := io.ReadAll(req.Body)
707 if err != nil {
708 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
709 return
710 }
711
712 _, success := parseRequest(w, requestBytes, "RequestAllDriverRankings", request_all_driver_rankings.GetRootAsRequestAllDriverRankings)
713 if !success {
714 return
715 }
716
717 rankings, err := handler.db.ReturnAllDriverRankings()
718 if err != nil {
719 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
720 return
721 }
722
723 var response RequestAllDriverRankingsResponseT
724 for _, ranking := range rankings {
725 response.DriverRankingList = append(response.DriverRankingList, &request_all_driver_rankings_response.RankingT{
726 MatchNumber: ranking.MatchNumber,
727 Rank1: ranking.Rank1,
728 Rank2: ranking.Rank2,
729 Rank3: ranking.Rank3,
730 })
731 }
732
733 builder := flatbuffers.NewBuilder(50 * 1024)
734 builder.Finish((&response).Pack(builder))
735 w.Write(builder.FinishedBytes())
736}
737
Philipp Schraderd3fac192022-03-02 20:35:46 -0800738func HandleRequests(db Database, scrape ScrapeMatchList, scoutingServer server.ScoutingServer) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800739 scoutingServer.HandleFunc("/requests", unknown)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800740 scoutingServer.Handle("/requests/submit/data_scouting", submitDataScoutingHandler{db})
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800741 scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
Filip Kujawaf882e022022-12-14 13:14:08 -0800742 scoutingServer.Handle("/requests/request/all_notes", requestAllNotesHandler{db})
743 scoutingServer.Handle("/requests/request/all_driver_rankings", requestAllDriverRankingsHandler{db})
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800744 scoutingServer.Handle("/requests/request/matches_for_team", requestMatchesForTeamHandler{db})
Philipp Schraderacf96232022-03-01 22:03:30 -0800745 scoutingServer.Handle("/requests/request/data_scouting", requestDataScoutingHandler{db})
Philipp Schraderd3fac192022-03-02 20:35:46 -0800746 scoutingServer.Handle("/requests/refresh_match_list", refreshMatchListHandler{db, scrape})
Alex Perry81f96ba2022-03-13 18:26:19 -0700747 scoutingServer.Handle("/requests/submit/submit_notes", submitNoteScoutingHandler{db})
748 scoutingServer.Handle("/requests/request/notes_for_team", requestNotesForTeamHandler{db})
Milo Lin1d59f0c2022-06-22 20:30:58 -0700749 scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
750 scoutingServer.Handle("/requests/request/shift_schedule", requestShiftScheduleHandler{db})
Filip Kujawa210a03b2022-11-24 14:41:11 -0800751 scoutingServer.Handle("/requests/submit/submit_driver_ranking", SubmitDriverRankingHandler{db})
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800752}