blob: 11fd31713ae3d87bed571a2afa9bba665c40c6f9 [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"
Sabina Leaver759090b2023-01-14 20:42:56 -080032 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_actions"
33 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_actions_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080034 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting"
Philipp Schrader30005e42022-03-06 13:53:58 -080035 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
Filip Kujawa210a03b2022-11-24 14:41:11 -080036 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking"
37 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070038 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes"
39 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070040 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule"
41 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080042 "github.com/frc971/971-Robot-Code/scouting/webserver/server"
43 flatbuffers "github.com/google/flatbuffers/go"
44)
45
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080046type SubmitDataScouting = submit_data_scouting.SubmitDataScouting
Philipp Schrader30005e42022-03-06 13:53:58 -080047type SubmitDataScoutingResponseT = submit_data_scouting_response.SubmitDataScoutingResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080048type RequestAllMatches = request_all_matches.RequestAllMatches
49type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
Filip Kujawaf882e022022-12-14 13:14:08 -080050type RequestAllDriverRankings = request_all_driver_rankings.RequestAllDriverRankings
51type RequestAllDriverRankingsResponseT = request_all_driver_rankings_response.RequestAllDriverRankingsResponseT
52type RequestAllNotes = request_all_notes.RequestAllNotes
53type RequestAllNotesResponseT = request_all_notes_response.RequestAllNotesResponseT
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080054type RequestMatchesForTeam = request_matches_for_team.RequestMatchesForTeam
55type RequestMatchesForTeamResponseT = request_matches_for_team_response.RequestMatchesForTeamResponseT
Philipp Schraderacf96232022-03-01 22:03:30 -080056type RequestDataScouting = request_data_scouting.RequestDataScouting
57type RequestDataScoutingResponseT = request_data_scouting_response.RequestDataScoutingResponseT
Philipp Schraderd3fac192022-03-02 20:35:46 -080058type RefreshMatchList = refresh_match_list.RefreshMatchList
59type RefreshMatchListResponseT = refresh_match_list_response.RefreshMatchListResponseT
Alex Perry81f96ba2022-03-13 18:26:19 -070060type SubmitNotes = submit_notes.SubmitNotes
61type SubmitNotesResponseT = submit_notes_response.SubmitNotesResponseT
62type RequestNotesForTeam = request_notes_for_team.RequestNotesForTeam
63type RequestNotesForTeamResponseT = request_notes_for_team_response.RequestNotesForTeamResponseT
Milo Lin1d59f0c2022-06-22 20:30:58 -070064type RequestShiftSchedule = request_shift_schedule.RequestShiftSchedule
65type RequestShiftScheduleResponseT = request_shift_schedule_response.RequestShiftScheduleResponseT
66type SubmitShiftSchedule = submit_shift_schedule.SubmitShiftSchedule
67type SubmitShiftScheduleResponseT = submit_shift_schedule_response.SubmitShiftScheduleResponseT
Filip Kujawa210a03b2022-11-24 14:41:11 -080068type SubmitDriverRanking = submit_driver_ranking.SubmitDriverRanking
69type SubmitDriverRankingResponseT = submit_driver_ranking_response.SubmitDriverRankingResponseT
Sabina Leaver759090b2023-01-14 20:42:56 -080070type SubmitActions = submit_actions.SubmitActions
71type SubmitActionsResponseT = submit_actions_response.SubmitActionsResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080072
Philipp Schrader8747f1b2022-02-23 23:56:22 -080073// The interface we expect the database abstraction to conform to.
74// We use an interface here because it makes unit testing easier.
75type Database interface {
76 AddToMatch(db.Match) error
Milo Lin1d59f0c2022-06-22 20:30:58 -070077 AddToShift(db.Shift) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080078 AddToStats(db.Stats) error
79 ReturnMatches() ([]db.Match, error)
Filip Kujawaf882e022022-12-14 13:14:08 -080080 ReturnAllNotes() ([]db.NotesData, error)
81 ReturnAllDriverRankings() ([]db.DriverRankingData, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070082 ReturnAllShifts() ([]db.Shift, error)
Philipp Schrader8747f1b2022-02-23 23:56:22 -080083 ReturnStats() ([]db.Stats, error)
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080084 QueryMatches(int32) ([]db.Match, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070085 QueryAllShifts(int) ([]db.Shift, error)
Philipp Schrader8747f1b2022-02-23 23:56:22 -080086 QueryStats(int) ([]db.Stats, error)
Philipp Schradereecb8962022-06-01 21:02:42 -070087 QueryNotes(int32) ([]string, error)
Filip Kujawaf947cb42022-11-21 10:00:30 -080088 AddNotes(db.NotesData) error
Filip Kujawa210a03b2022-11-24 14:41:11 -080089 AddDriverRanking(db.DriverRankingData) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080090}
91
Philipp Schraderd3fac192022-03-02 20:35:46 -080092type ScrapeMatchList func(int32, string) ([]scraping.Match, error)
93
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080094// Handles unknown requests. Just returns a 404.
95func unknown(w http.ResponseWriter, req *http.Request) {
96 w.WriteHeader(http.StatusNotFound)
97}
98
99func respondWithError(w http.ResponseWriter, statusCode int, errorMessage string) {
100 builder := flatbuffers.NewBuilder(1024)
101 builder.Finish((&error_response.ErrorResponseT{
102 ErrorMessage: errorMessage,
103 }).Pack(builder))
104 w.WriteHeader(statusCode)
105 w.Write(builder.FinishedBytes())
106}
107
108func respondNotImplemented(w http.ResponseWriter) {
109 respondWithError(w, http.StatusNotImplemented, "")
110}
111
Philipp Schraderb7e75932022-03-26 16:18:34 -0700112func 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 -0800113 success := true
114 defer func() {
115 if r := recover(); r != nil {
Philipp Schraderb7e75932022-03-26 16:18:34 -0700116 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse %s: %v", requestName, r))
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800117 success = false
118 }
119 }()
Philipp Schraderb7e75932022-03-26 16:18:34 -0700120 result := parser(buf, 0)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800121 return result, success
122}
123
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700124// Parses the authorization information that the browser inserts into the
125// headers. The authorization follows this format:
126//
127// req.Headers["Authorization"] = []string{"Basic <base64 encoded username:password>"}
128func parseUsername(req *http.Request) string {
129 auth, ok := req.Header["Authorization"]
130 if !ok {
131 return "unknown"
132 }
133
134 parts := strings.Split(auth[0], " ")
135 if !(len(parts) == 2 && parts[0] == "Basic") {
136 return "unknown"
137 }
138
139 info, err := base64.StdEncoding.DecodeString(parts[1])
140 if err != nil {
141 log.Println("ERROR: Failed to parse Basic authentication.")
142 return "unknown"
143 }
144
145 loginParts := strings.Split(string(info), ":")
146 if len(loginParts) != 2 {
147 return "unknown"
148 }
149 return loginParts[0]
150}
151
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800152// Handles a SubmitDataScouting request.
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800153type submitDataScoutingHandler struct {
154 db Database
155}
156
157func (handler submitDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700158 // Get the username of the person submitting the data.
159 username := parseUsername(req)
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700160
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800161 requestBytes, err := io.ReadAll(req.Body)
162 if err != nil {
163 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
164 return
165 }
166
Philipp Schraderb7e75932022-03-26 16:18:34 -0700167 request, success := parseRequest[SubmitDataScouting](w, requestBytes, "SubmitDataScouting", submit_data_scouting.GetRootAsSubmitDataScouting)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800168 if !success {
169 return
170 }
171
Philipp Schraderd7b6eba2022-03-18 22:21:25 -0700172 log.Println("Got data scouting data for match", request.Match(), "team", request.Team(), "from", username)
173
Philipp Schrader30005e42022-03-06 13:53:58 -0800174 stats := db.Stats{
Philipp Schraderfee07e12022-03-17 22:19:47 -0700175 TeamNumber: request.Team(),
176 MatchNumber: request.Match(),
Philipp Schrader30b4a682022-04-16 14:36:17 -0700177 SetNumber: request.SetNumber(),
Philipp Schrader4535b7e2022-04-08 20:27:00 -0700178 CompLevel: string(request.CompLevel()),
Philipp Schraderfee07e12022-03-17 22:19:47 -0700179 StartingQuadrant: request.StartingQuadrant(),
180 AutoBallPickedUp: [5]bool{
181 request.AutoBall1(), request.AutoBall2(), request.AutoBall3(),
182 request.AutoBall4(), request.AutoBall5(),
183 },
Philipp Schraderfa45d742022-03-18 19:29:05 -0700184 ShotsMissedAuto: request.MissedShotsAuto(),
185 UpperGoalAuto: request.UpperGoalAuto(),
186 LowerGoalAuto: request.LowerGoalAuto(),
187 ShotsMissed: request.MissedShotsTele(),
188 UpperGoalShots: request.UpperGoalTele(),
189 LowerGoalShots: request.LowerGoalTele(),
190 PlayedDefense: request.DefenseRating(),
191 DefenseReceivedScore: request.DefenseReceivedRating(),
192 Climbing: int32(request.ClimbLevel()),
193 CollectedBy: username,
194 Comment: string(request.Comment()),
Philipp Schrader30005e42022-03-06 13:53:58 -0800195 }
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800196
Philipp Schraderfee07e12022-03-17 22:19:47 -0700197 // Do some error checking.
198 if stats.StartingQuadrant < 1 || stats.StartingQuadrant > 4 {
199 respondWithError(w, http.StatusBadRequest, fmt.Sprint(
200 "Invalid starting_quadrant field value of ", stats.StartingQuadrant))
201 return
202 }
203
Philipp Schrader30005e42022-03-06 13:53:58 -0800204 err = handler.db.AddToStats(stats)
205 if err != nil {
206 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit datascouting data: ", err))
Philipp Schraderfee07e12022-03-17 22:19:47 -0700207 return
Philipp Schrader30005e42022-03-06 13:53:58 -0800208 }
209
210 builder := flatbuffers.NewBuilder(50 * 1024)
211 builder.Finish((&SubmitDataScoutingResponseT{}).Pack(builder))
212 w.Write(builder.FinishedBytes())
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800213}
214
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800215// Handles a RequestAllMaches request.
216type requestAllMatchesHandler struct {
217 db Database
218}
219
220func (handler requestAllMatchesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
221 requestBytes, err := io.ReadAll(req.Body)
222 if err != nil {
223 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
224 return
225 }
226
Philipp Schraderb7e75932022-03-26 16:18:34 -0700227 _, success := parseRequest(w, requestBytes, "RequestAllMatches", request_all_matches.GetRootAsRequestAllMatches)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800228 if !success {
229 return
230 }
231
232 matches, err := handler.db.ReturnMatches()
233 if err != nil {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700234 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800235 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800236 }
237
238 var response RequestAllMatchesResponseT
239 for _, match := range matches {
240 response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
241 MatchNumber: match.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700242 SetNumber: match.SetNumber,
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800243 CompLevel: match.CompLevel,
244 R1: match.R1,
245 R2: match.R2,
246 R3: match.R3,
247 B1: match.B1,
248 B2: match.B2,
249 B3: match.B3,
250 })
251 }
252
253 builder := flatbuffers.NewBuilder(50 * 1024)
254 builder.Finish((&response).Pack(builder))
255 w.Write(builder.FinishedBytes())
256}
257
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800258// Handles a RequestMatchesForTeam request.
259type requestMatchesForTeamHandler struct {
260 db Database
261}
262
263func (handler requestMatchesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
264 requestBytes, err := io.ReadAll(req.Body)
265 if err != nil {
266 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
267 return
268 }
269
Philipp Schraderb7e75932022-03-26 16:18:34 -0700270 request, success := parseRequest(w, requestBytes, "RequestMatchesForTeam", request_matches_for_team.GetRootAsRequestMatchesForTeam)
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800271 if !success {
272 return
273 }
274
275 matches, err := handler.db.QueryMatches(request.Team())
276 if err != nil {
277 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800278 return
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800279 }
280
281 var response RequestAllMatchesResponseT
282 for _, match := range matches {
283 response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
284 MatchNumber: match.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700285 SetNumber: match.SetNumber,
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800286 CompLevel: match.CompLevel,
287 R1: match.R1,
288 R2: match.R2,
289 R3: match.R3,
290 B1: match.B1,
291 B2: match.B2,
292 B3: match.B3,
293 })
294 }
295
296 builder := flatbuffers.NewBuilder(50 * 1024)
297 builder.Finish((&response).Pack(builder))
298 w.Write(builder.FinishedBytes())
299}
300
Philipp Schraderacf96232022-03-01 22:03:30 -0800301// Handles a RequestDataScouting request.
302type requestDataScoutingHandler struct {
303 db Database
304}
305
306func (handler requestDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
307 requestBytes, err := io.ReadAll(req.Body)
308 if err != nil {
309 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
310 return
311 }
312
Philipp Schraderb7e75932022-03-26 16:18:34 -0700313 _, success := parseRequest(w, requestBytes, "RequestDataScouting", request_data_scouting.GetRootAsRequestDataScouting)
Philipp Schraderacf96232022-03-01 22:03:30 -0800314 if !success {
315 return
316 }
317
318 stats, err := handler.db.ReturnStats()
319 if err != nil {
320 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800321 return
Philipp Schraderacf96232022-03-01 22:03:30 -0800322 }
323
324 var response RequestDataScoutingResponseT
325 for _, stat := range stats {
326 response.StatsList = append(response.StatsList, &request_data_scouting_response.StatsT{
Philipp Schraderfa45d742022-03-18 19:29:05 -0700327 Team: stat.TeamNumber,
328 Match: stat.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700329 SetNumber: stat.SetNumber,
Philipp Schrader4535b7e2022-04-08 20:27:00 -0700330 CompLevel: stat.CompLevel,
Philipp Schraderfa45d742022-03-18 19:29:05 -0700331 StartingQuadrant: stat.StartingQuadrant,
332 AutoBall1: stat.AutoBallPickedUp[0],
333 AutoBall2: stat.AutoBallPickedUp[1],
334 AutoBall3: stat.AutoBallPickedUp[2],
335 AutoBall4: stat.AutoBallPickedUp[3],
336 AutoBall5: stat.AutoBallPickedUp[4],
337 MissedShotsAuto: stat.ShotsMissedAuto,
338 UpperGoalAuto: stat.UpperGoalAuto,
339 LowerGoalAuto: stat.LowerGoalAuto,
340 MissedShotsTele: stat.ShotsMissed,
341 UpperGoalTele: stat.UpperGoalShots,
342 LowerGoalTele: stat.LowerGoalShots,
343 DefenseRating: stat.PlayedDefense,
344 DefenseReceivedRating: stat.DefenseReceivedScore,
345 ClimbLevel: request_data_scouting_response.ClimbLevel(stat.Climbing),
346 CollectedBy: stat.CollectedBy,
347 Comment: stat.Comment,
Philipp Schraderacf96232022-03-01 22:03:30 -0800348 })
349 }
350
351 builder := flatbuffers.NewBuilder(50 * 1024)
352 builder.Finish((&response).Pack(builder))
353 w.Write(builder.FinishedBytes())
354}
355
Philipp Schraderd3fac192022-03-02 20:35:46 -0800356func parseTeamKey(teamKey string) (int, error) {
357 // TBA prefixes teams with "frc". Not sure why. Get rid of that.
358 teamKey = strings.TrimPrefix(teamKey, "frc")
Philipp Schrader84fe7782022-11-12 08:10:36 -0800359 magnitude := 0
360 if strings.HasSuffix(teamKey, "A") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800361 magnitude = 0
Philipp Schrader84fe7782022-11-12 08:10:36 -0800362 teamKey = strings.TrimSuffix(teamKey, "A")
363 } else if strings.HasSuffix(teamKey, "B") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800364 magnitude = 9
Philipp Schrader84fe7782022-11-12 08:10:36 -0800365 teamKey = strings.TrimSuffix(teamKey, "B")
366 } else if strings.HasSuffix(teamKey, "C") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800367 magnitude = 8
Philipp Schrader84fe7782022-11-12 08:10:36 -0800368 teamKey = strings.TrimSuffix(teamKey, "C")
369 } else if strings.HasSuffix(teamKey, "D") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800370 magnitude = 7
Philipp Schrader84fe7782022-11-12 08:10:36 -0800371 teamKey = strings.TrimSuffix(teamKey, "D")
372 } else if strings.HasSuffix(teamKey, "E") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800373 magnitude = 6
Philipp Schrader84fe7782022-11-12 08:10:36 -0800374 teamKey = strings.TrimSuffix(teamKey, "E")
375 } else if strings.HasSuffix(teamKey, "F") {
Filip Kujawa08057fa2022-11-12 09:54:49 -0800376 magnitude = 5
Philipp Schrader84fe7782022-11-12 08:10:36 -0800377 teamKey = strings.TrimSuffix(teamKey, "F")
378 }
Filip Kujawa4de6feb2022-11-12 08:41:49 -0800379
380 if magnitude != 0 {
381 teamKey = strconv.Itoa(magnitude) + teamKey
Philipp Schrader84fe7782022-11-12 08:10:36 -0800382 }
Filip Kujawa4de6feb2022-11-12 08:41:49 -0800383
384 result, err := strconv.Atoi(teamKey)
Philipp Schrader84fe7782022-11-12 08:10:36 -0800385 return result, err
Philipp Schraderd3fac192022-03-02 20:35:46 -0800386}
387
388// Parses the alliance data from the specified match and returns the three red
389// teams and the three blue teams.
390func parseTeamKeys(match *scraping.Match) ([3]int32, [3]int32, error) {
391 redKeys := match.Alliances.Red.TeamKeys
392 blueKeys := match.Alliances.Blue.TeamKeys
393
394 if len(redKeys) != 3 || len(blueKeys) != 3 {
395 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
396 "Found %d red teams and %d blue teams.", len(redKeys), len(blueKeys)))
397 }
398
399 var red [3]int32
400 for i, key := range redKeys {
401 team, err := parseTeamKey(key)
402 if err != nil {
403 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
404 "Failed to parse red %d team '%s' as integer: %v", i+1, key, err))
405 }
406 red[i] = int32(team)
407 }
408 var blue [3]int32
409 for i, key := range blueKeys {
410 team, err := parseTeamKey(key)
411 if err != nil {
412 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
413 "Failed to parse blue %d team '%s' as integer: %v", i+1, key, err))
414 }
415 blue[i] = int32(team)
416 }
417 return red, blue, nil
418}
419
420type refreshMatchListHandler struct {
421 db Database
422 scrape ScrapeMatchList
423}
424
425func (handler refreshMatchListHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
426 requestBytes, err := io.ReadAll(req.Body)
427 if err != nil {
428 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
429 return
430 }
431
Philipp Schraderb7e75932022-03-26 16:18:34 -0700432 request, success := parseRequest(w, requestBytes, "RefreshMatchList", refresh_match_list.GetRootAsRefreshMatchList)
Philipp Schraderd3fac192022-03-02 20:35:46 -0800433 if !success {
434 return
435 }
436
437 matches, err := handler.scrape(request.Year(), string(request.EventCode()))
438 if err != nil {
439 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to scrape match list: ", err))
440 return
441 }
442
443 for _, match := range matches {
444 // Make sure the data is valid.
445 red, blue, err := parseTeamKeys(&match)
446 if err != nil {
447 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
448 "TheBlueAlliance data for match %d is malformed: %v", match.MatchNumber, err))
449 return
450 }
451 // Add the match to the database.
Philipp Schrader7365d322022-03-06 16:40:08 -0800452 err = handler.db.AddToMatch(db.Match{
Philipp Schraderd3fac192022-03-02 20:35:46 -0800453 MatchNumber: int32(match.MatchNumber),
Philipp Schrader30b4a682022-04-16 14:36:17 -0700454 SetNumber: int32(match.SetNumber),
Philipp Schrader45befdd2022-04-08 19:12:44 -0700455 CompLevel: match.CompLevel,
456 R1: red[0],
457 R2: red[1],
458 R3: red[2],
459 B1: blue[0],
460 B2: blue[1],
461 B3: blue[2],
Philipp Schraderd3fac192022-03-02 20:35:46 -0800462 })
Philipp Schrader7365d322022-03-06 16:40:08 -0800463 if err != nil {
464 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
465 "Failed to add match %d to the database: %v", match.MatchNumber, err))
466 return
467 }
Philipp Schraderd3fac192022-03-02 20:35:46 -0800468 }
469
470 var response RefreshMatchListResponseT
471 builder := flatbuffers.NewBuilder(1024)
472 builder.Finish((&response).Pack(builder))
473 w.Write(builder.FinishedBytes())
474}
475
Alex Perry81f96ba2022-03-13 18:26:19 -0700476type submitNoteScoutingHandler struct {
477 db Database
478}
479
480func (handler submitNoteScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
481 requestBytes, err := io.ReadAll(req.Body)
482 if err != nil {
483 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
484 return
485 }
486
Philipp Schraderb7e75932022-03-26 16:18:34 -0700487 request, success := parseRequest(w, requestBytes, "SubmitNotes", submit_notes.GetRootAsSubmitNotes)
Alex Perry81f96ba2022-03-13 18:26:19 -0700488 if !success {
489 return
490 }
491
Filip Kujawaf947cb42022-11-21 10:00:30 -0800492 err = handler.db.AddNotes(db.NotesData{
493 TeamNumber: request.Team(),
494 Notes: string(request.Notes()),
495 GoodDriving: bool(request.GoodDriving()),
496 BadDriving: bool(request.BadDriving()),
497 SketchyClimb: bool(request.SketchyClimb()),
498 SolidClimb: bool(request.SolidClimb()),
499 GoodDefense: bool(request.GoodDefense()),
500 BadDefense: bool(request.BadDefense()),
501 })
Alex Perry81f96ba2022-03-13 18:26:19 -0700502 if err != nil {
503 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert notes: %v", err))
504 return
505 }
506
507 var response SubmitNotesResponseT
508 builder := flatbuffers.NewBuilder(10)
509 builder.Finish((&response).Pack(builder))
510 w.Write(builder.FinishedBytes())
511}
512
Alex Perry81f96ba2022-03-13 18:26:19 -0700513type requestNotesForTeamHandler struct {
514 db Database
515}
516
517func (handler requestNotesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
518 requestBytes, err := io.ReadAll(req.Body)
519 if err != nil {
520 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
521 return
522 }
523
Philipp Schraderb7e75932022-03-26 16:18:34 -0700524 request, success := parseRequest(w, requestBytes, "RequestNotesForTeam", request_notes_for_team.GetRootAsRequestNotesForTeam)
Alex Perry81f96ba2022-03-13 18:26:19 -0700525 if !success {
526 return
527 }
528
Philipp Schradereecb8962022-06-01 21:02:42 -0700529 notes, err := handler.db.QueryNotes(request.Team())
Alex Perry81f96ba2022-03-13 18:26:19 -0700530 if err != nil {
531 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query notes: %v", err))
532 return
533 }
534
535 var response RequestNotesForTeamResponseT
Philipp Schradereecb8962022-06-01 21:02:42 -0700536 for _, data := range notes {
Alex Perry81f96ba2022-03-13 18:26:19 -0700537 response.Notes = append(response.Notes, &request_notes_for_team_response.NoteT{data})
538 }
539
540 builder := flatbuffers.NewBuilder(1024)
541 builder.Finish((&response).Pack(builder))
542 w.Write(builder.FinishedBytes())
543}
544
Milo Lin1d59f0c2022-06-22 20:30:58 -0700545type requestShiftScheduleHandler struct {
546 db Database
547}
548
549func (handler requestShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
550 requestBytes, err := io.ReadAll(req.Body)
551 if err != nil {
552 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
553 return
554 }
555
556 _, success := parseRequest(w, requestBytes, "RequestShiftSchedule", request_shift_schedule.GetRootAsRequestShiftSchedule)
557 if !success {
558 return
559 }
560
561 shiftData, err := handler.db.ReturnAllShifts()
562 if err != nil {
563 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query shift schedule: %v", err))
564 return
565 }
566
567 var response RequestShiftScheduleResponseT
568 for _, shifts := range shiftData {
569 response.ShiftSchedule = append(response.ShiftSchedule, &request_shift_schedule_response.MatchAssignmentT{
570 MatchNumber: shifts.MatchNumber,
571 R1scouter: shifts.R1scouter,
572 R2scouter: shifts.R2scouter,
573 R3scouter: shifts.R3scouter,
574 B1scouter: shifts.B1scouter,
575 B2scouter: shifts.B2scouter,
576 B3scouter: shifts.B3scouter,
577 })
578 }
579
580 builder := flatbuffers.NewBuilder(1024)
581 builder.Finish((&response).Pack(builder))
582 w.Write(builder.FinishedBytes())
583}
584
585type submitShiftScheduleHandler struct {
586 db Database
587}
588
589func (handler submitShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
590 // Get the username of the person submitting the data.
591 username := parseUsername(req)
592
593 requestBytes, err := io.ReadAll(req.Body)
594 if err != nil {
595 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
596 return
597 }
598
599 request, success := parseRequest[SubmitShiftSchedule](w, requestBytes, "SubmitShiftSchedule", submit_shift_schedule.GetRootAsSubmitShiftSchedule)
600 if !success {
601 return
602 }
603
604 log.Println("Got shift schedule from", username)
605 shift_schedule_length := request.ShiftScheduleLength()
606 for i := 0; i < shift_schedule_length; i++ {
607 var match_assignment submit_shift_schedule.MatchAssignment
608 request.ShiftSchedule(&match_assignment, i)
609 current_shift := db.Shift{
610 MatchNumber: match_assignment.MatchNumber(),
611 R1scouter: string(match_assignment.R1scouter()),
612 R2scouter: string(match_assignment.R2scouter()),
613 R3scouter: string(match_assignment.R3scouter()),
614 B1scouter: string(match_assignment.B1scouter()),
615 B2scouter: string(match_assignment.B2scouter()),
616 B3scouter: string(match_assignment.B3scouter()),
617 }
618 err = handler.db.AddToShift(current_shift)
619 if err != nil {
620 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit shift schedule: ", err))
621 return
622 }
623 }
624
625 builder := flatbuffers.NewBuilder(50 * 1024)
626 builder.Finish((&SubmitShiftScheduleResponseT{}).Pack(builder))
627 w.Write(builder.FinishedBytes())
628}
629
Filip Kujawa210a03b2022-11-24 14:41:11 -0800630type SubmitDriverRankingHandler struct {
631 db Database
632}
633
634func (handler SubmitDriverRankingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
635 requestBytes, err := io.ReadAll(req.Body)
636 if err != nil {
637 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
638 return
639 }
640
641 request, success := parseRequest(w, requestBytes, "SubmitDriverRanking", submit_driver_ranking.GetRootAsSubmitDriverRanking)
642 if !success {
643 return
644 }
645
646 err = handler.db.AddDriverRanking(db.DriverRankingData{
647 MatchNumber: request.MatchNumber(),
648 Rank1: request.Rank1(),
649 Rank2: request.Rank2(),
650 Rank3: request.Rank3(),
651 })
652
653 if err != nil {
654 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert driver ranking: %v", err))
655 return
656 }
657
658 var response SubmitDriverRankingResponseT
659 builder := flatbuffers.NewBuilder(10)
660 builder.Finish((&response).Pack(builder))
661 w.Write(builder.FinishedBytes())
662}
663
Filip Kujawaf882e022022-12-14 13:14:08 -0800664type requestAllNotesHandler struct {
665 db Database
666}
667
668func (handler requestAllNotesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
669 requestBytes, err := io.ReadAll(req.Body)
670 if err != nil {
671 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
672 return
673 }
674
675 _, success := parseRequest(w, requestBytes, "RequestAllNotes", request_all_notes.GetRootAsRequestAllNotes)
676 if !success {
677 return
678 }
679
680 notes, err := handler.db.ReturnAllNotes()
681 if err != nil {
682 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
683 return
684 }
685
686 var response RequestAllNotesResponseT
687 for _, note := range notes {
688 response.NoteList = append(response.NoteList, &request_all_notes_response.NoteT{
689 Team: note.TeamNumber,
690 Notes: note.Notes,
691 GoodDriving: note.GoodDriving,
692 BadDriving: note.BadDriving,
693 SketchyClimb: note.SketchyClimb,
694 SolidClimb: note.SolidClimb,
695 GoodDefense: note.GoodDefense,
696 BadDefense: note.BadDefense,
697 })
698 }
699
700 builder := flatbuffers.NewBuilder(50 * 1024)
701 builder.Finish((&response).Pack(builder))
702 w.Write(builder.FinishedBytes())
703}
704
705type requestAllDriverRankingsHandler struct {
706 db Database
707}
708
709func (handler requestAllDriverRankingsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
710 requestBytes, err := io.ReadAll(req.Body)
711 if err != nil {
712 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
713 return
714 }
715
716 _, success := parseRequest(w, requestBytes, "RequestAllDriverRankings", request_all_driver_rankings.GetRootAsRequestAllDriverRankings)
717 if !success {
718 return
719 }
720
721 rankings, err := handler.db.ReturnAllDriverRankings()
722 if err != nil {
723 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
724 return
725 }
726
727 var response RequestAllDriverRankingsResponseT
728 for _, ranking := range rankings {
729 response.DriverRankingList = append(response.DriverRankingList, &request_all_driver_rankings_response.RankingT{
730 MatchNumber: ranking.MatchNumber,
731 Rank1: ranking.Rank1,
732 Rank2: ranking.Rank2,
733 Rank3: ranking.Rank3,
734 })
735 }
736
737 builder := flatbuffers.NewBuilder(50 * 1024)
738 builder.Finish((&response).Pack(builder))
739 w.Write(builder.FinishedBytes())
740}
741
Philipp Schraderd3fac192022-03-02 20:35:46 -0800742func HandleRequests(db Database, scrape ScrapeMatchList, scoutingServer server.ScoutingServer) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800743 scoutingServer.HandleFunc("/requests", unknown)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800744 scoutingServer.Handle("/requests/submit/data_scouting", submitDataScoutingHandler{db})
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800745 scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
Filip Kujawaf882e022022-12-14 13:14:08 -0800746 scoutingServer.Handle("/requests/request/all_notes", requestAllNotesHandler{db})
747 scoutingServer.Handle("/requests/request/all_driver_rankings", requestAllDriverRankingsHandler{db})
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800748 scoutingServer.Handle("/requests/request/matches_for_team", requestMatchesForTeamHandler{db})
Philipp Schraderacf96232022-03-01 22:03:30 -0800749 scoutingServer.Handle("/requests/request/data_scouting", requestDataScoutingHandler{db})
Philipp Schraderd3fac192022-03-02 20:35:46 -0800750 scoutingServer.Handle("/requests/refresh_match_list", refreshMatchListHandler{db, scrape})
Alex Perry81f96ba2022-03-13 18:26:19 -0700751 scoutingServer.Handle("/requests/submit/submit_notes", submitNoteScoutingHandler{db})
752 scoutingServer.Handle("/requests/request/notes_for_team", requestNotesForTeamHandler{db})
Milo Lin1d59f0c2022-06-22 20:30:58 -0700753 scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
754 scoutingServer.Handle("/requests/request/shift_schedule", requestShiftScheduleHandler{db})
Filip Kujawa210a03b2022-11-24 14:41:11 -0800755 scoutingServer.Handle("/requests/submit/submit_driver_ranking", SubmitDriverRankingHandler{db})
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800756}