blob: 67a172289a1046117f2cd5e20667096783ee4144 [file] [log] [blame]
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08001package requests
2
3import (
Philipp Schraderfae8a7e2022-03-13 22:51:54 -07004 "encoding/base64"
Philipp Schraderd3fac192022-03-02 20:35:46 -08005 "errors"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08006 "fmt"
7 "io"
Philipp Schraderfae8a7e2022-03-13 22:51:54 -07008 "log"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08009 "net/http"
Philipp Schraderd3fac192022-03-02 20:35:46 -080010 "strconv"
11 "strings"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080012
Philipp Schrader8747f1b2022-02-23 23:56:22 -080013 "github.com/frc971/971-Robot-Code/scouting/db"
Philipp Schraderd3fac192022-03-02 20:35:46 -080014 "github.com/frc971/971-Robot-Code/scouting/scraping"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080015 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
Philipp Schraderd3fac192022-03-02 20:35:46 -080016 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/refresh_match_list"
17 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/refresh_match_list_response"
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080018 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches"
19 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches_response"
Philipp Schraderacf96232022-03-01 22:03:30 -080020 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_data_scouting"
21 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_data_scouting_response"
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080022 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team"
23 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070024 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team"
25 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070026 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule"
27 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080028 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting"
Philipp Schrader30005e42022-03-06 13:53:58 -080029 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070030 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes"
31 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070032 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule"
33 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080034 "github.com/frc971/971-Robot-Code/scouting/webserver/server"
35 flatbuffers "github.com/google/flatbuffers/go"
36)
37
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080038type SubmitDataScouting = submit_data_scouting.SubmitDataScouting
Philipp Schrader30005e42022-03-06 13:53:58 -080039type SubmitDataScoutingResponseT = submit_data_scouting_response.SubmitDataScoutingResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080040type RequestAllMatches = request_all_matches.RequestAllMatches
41type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080042type RequestMatchesForTeam = request_matches_for_team.RequestMatchesForTeam
43type RequestMatchesForTeamResponseT = request_matches_for_team_response.RequestMatchesForTeamResponseT
Philipp Schraderacf96232022-03-01 22:03:30 -080044type RequestDataScouting = request_data_scouting.RequestDataScouting
45type RequestDataScoutingResponseT = request_data_scouting_response.RequestDataScoutingResponseT
Philipp Schraderd3fac192022-03-02 20:35:46 -080046type RefreshMatchList = refresh_match_list.RefreshMatchList
47type RefreshMatchListResponseT = refresh_match_list_response.RefreshMatchListResponseT
Alex Perry81f96ba2022-03-13 18:26:19 -070048type SubmitNotes = submit_notes.SubmitNotes
49type SubmitNotesResponseT = submit_notes_response.SubmitNotesResponseT
50type RequestNotesForTeam = request_notes_for_team.RequestNotesForTeam
51type RequestNotesForTeamResponseT = request_notes_for_team_response.RequestNotesForTeamResponseT
Milo Lin1d59f0c2022-06-22 20:30:58 -070052type RequestShiftSchedule = request_shift_schedule.RequestShiftSchedule
53type RequestShiftScheduleResponseT = request_shift_schedule_response.RequestShiftScheduleResponseT
54type SubmitShiftSchedule = submit_shift_schedule.SubmitShiftSchedule
55type SubmitShiftScheduleResponseT = submit_shift_schedule_response.SubmitShiftScheduleResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080056
Philipp Schrader8747f1b2022-02-23 23:56:22 -080057// The interface we expect the database abstraction to conform to.
58// We use an interface here because it makes unit testing easier.
59type Database interface {
60 AddToMatch(db.Match) error
Milo Lin1d59f0c2022-06-22 20:30:58 -070061 AddToShift(db.Shift) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080062 AddToStats(db.Stats) error
63 ReturnMatches() ([]db.Match, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070064 ReturnAllShifts() ([]db.Shift, error)
Philipp Schrader8747f1b2022-02-23 23:56:22 -080065 ReturnStats() ([]db.Stats, error)
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080066 QueryMatches(int32) ([]db.Match, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070067 QueryAllShifts(int) ([]db.Shift, error)
Philipp Schrader8747f1b2022-02-23 23:56:22 -080068 QueryStats(int) ([]db.Stats, error)
Alex Perry81f96ba2022-03-13 18:26:19 -070069 QueryNotes(int32) (db.NotesData, error)
70 AddNotes(db.NotesData) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080071}
72
Philipp Schraderd3fac192022-03-02 20:35:46 -080073type ScrapeMatchList func(int32, string) ([]scraping.Match, error)
74
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080075// Handles unknown requests. Just returns a 404.
76func unknown(w http.ResponseWriter, req *http.Request) {
77 w.WriteHeader(http.StatusNotFound)
78}
79
80func respondWithError(w http.ResponseWriter, statusCode int, errorMessage string) {
81 builder := flatbuffers.NewBuilder(1024)
82 builder.Finish((&error_response.ErrorResponseT{
83 ErrorMessage: errorMessage,
84 }).Pack(builder))
85 w.WriteHeader(statusCode)
86 w.Write(builder.FinishedBytes())
87}
88
89func respondNotImplemented(w http.ResponseWriter) {
90 respondWithError(w, http.StatusNotImplemented, "")
91}
92
Philipp Schraderb7e75932022-03-26 16:18:34 -070093func 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 -080094 success := true
95 defer func() {
96 if r := recover(); r != nil {
Philipp Schraderb7e75932022-03-26 16:18:34 -070097 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse %s: %v", requestName, r))
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080098 success = false
99 }
100 }()
Philipp Schraderb7e75932022-03-26 16:18:34 -0700101 result := parser(buf, 0)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800102 return result, success
103}
104
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700105// Parses the authorization information that the browser inserts into the
106// headers. The authorization follows this format:
107//
108// req.Headers["Authorization"] = []string{"Basic <base64 encoded username:password>"}
109func parseUsername(req *http.Request) string {
110 auth, ok := req.Header["Authorization"]
111 if !ok {
112 return "unknown"
113 }
114
115 parts := strings.Split(auth[0], " ")
116 if !(len(parts) == 2 && parts[0] == "Basic") {
117 return "unknown"
118 }
119
120 info, err := base64.StdEncoding.DecodeString(parts[1])
121 if err != nil {
122 log.Println("ERROR: Failed to parse Basic authentication.")
123 return "unknown"
124 }
125
126 loginParts := strings.Split(string(info), ":")
127 if len(loginParts) != 2 {
128 return "unknown"
129 }
130 return loginParts[0]
131}
132
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800133// Handles a SubmitDataScouting request.
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800134type submitDataScoutingHandler struct {
135 db Database
136}
137
138func (handler submitDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700139 // Get the username of the person submitting the data.
140 username := parseUsername(req)
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700141
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800142 requestBytes, err := io.ReadAll(req.Body)
143 if err != nil {
144 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
145 return
146 }
147
Philipp Schraderb7e75932022-03-26 16:18:34 -0700148 request, success := parseRequest[SubmitDataScouting](w, requestBytes, "SubmitDataScouting", submit_data_scouting.GetRootAsSubmitDataScouting)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800149 if !success {
150 return
151 }
152
Philipp Schraderd7b6eba2022-03-18 22:21:25 -0700153 log.Println("Got data scouting data for match", request.Match(), "team", request.Team(), "from", username)
154
Philipp Schrader30005e42022-03-06 13:53:58 -0800155 stats := db.Stats{
Philipp Schraderfee07e12022-03-17 22:19:47 -0700156 TeamNumber: request.Team(),
157 MatchNumber: request.Match(),
Philipp Schrader30b4a682022-04-16 14:36:17 -0700158 SetNumber: request.SetNumber(),
Philipp Schrader4535b7e2022-04-08 20:27:00 -0700159 CompLevel: string(request.CompLevel()),
Philipp Schraderfee07e12022-03-17 22:19:47 -0700160 StartingQuadrant: request.StartingQuadrant(),
161 AutoBallPickedUp: [5]bool{
162 request.AutoBall1(), request.AutoBall2(), request.AutoBall3(),
163 request.AutoBall4(), request.AutoBall5(),
164 },
Philipp Schraderfa45d742022-03-18 19:29:05 -0700165 ShotsMissedAuto: request.MissedShotsAuto(),
166 UpperGoalAuto: request.UpperGoalAuto(),
167 LowerGoalAuto: request.LowerGoalAuto(),
168 ShotsMissed: request.MissedShotsTele(),
169 UpperGoalShots: request.UpperGoalTele(),
170 LowerGoalShots: request.LowerGoalTele(),
171 PlayedDefense: request.DefenseRating(),
172 DefenseReceivedScore: request.DefenseReceivedRating(),
173 Climbing: int32(request.ClimbLevel()),
174 CollectedBy: username,
175 Comment: string(request.Comment()),
Philipp Schrader30005e42022-03-06 13:53:58 -0800176 }
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800177
Philipp Schraderfee07e12022-03-17 22:19:47 -0700178 // Do some error checking.
179 if stats.StartingQuadrant < 1 || stats.StartingQuadrant > 4 {
180 respondWithError(w, http.StatusBadRequest, fmt.Sprint(
181 "Invalid starting_quadrant field value of ", stats.StartingQuadrant))
182 return
183 }
184
Philipp Schrader30005e42022-03-06 13:53:58 -0800185 err = handler.db.AddToStats(stats)
186 if err != nil {
187 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit datascouting data: ", err))
Philipp Schraderfee07e12022-03-17 22:19:47 -0700188 return
Philipp Schrader30005e42022-03-06 13:53:58 -0800189 }
190
191 builder := flatbuffers.NewBuilder(50 * 1024)
192 builder.Finish((&SubmitDataScoutingResponseT{}).Pack(builder))
193 w.Write(builder.FinishedBytes())
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800194}
195
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800196// Handles a RequestAllMaches request.
197type requestAllMatchesHandler struct {
198 db Database
199}
200
201func (handler requestAllMatchesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
202 requestBytes, err := io.ReadAll(req.Body)
203 if err != nil {
204 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
205 return
206 }
207
Philipp Schraderb7e75932022-03-26 16:18:34 -0700208 _, success := parseRequest(w, requestBytes, "RequestAllMatches", request_all_matches.GetRootAsRequestAllMatches)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800209 if !success {
210 return
211 }
212
213 matches, err := handler.db.ReturnMatches()
214 if err != nil {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700215 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800216 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800217 }
218
219 var response RequestAllMatchesResponseT
220 for _, match := range matches {
221 response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
222 MatchNumber: match.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700223 SetNumber: match.SetNumber,
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800224 CompLevel: match.CompLevel,
225 R1: match.R1,
226 R2: match.R2,
227 R3: match.R3,
228 B1: match.B1,
229 B2: match.B2,
230 B3: match.B3,
231 })
232 }
233
234 builder := flatbuffers.NewBuilder(50 * 1024)
235 builder.Finish((&response).Pack(builder))
236 w.Write(builder.FinishedBytes())
237}
238
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800239// Handles a RequestMatchesForTeam request.
240type requestMatchesForTeamHandler struct {
241 db Database
242}
243
244func (handler requestMatchesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
245 requestBytes, err := io.ReadAll(req.Body)
246 if err != nil {
247 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
248 return
249 }
250
Philipp Schraderb7e75932022-03-26 16:18:34 -0700251 request, success := parseRequest(w, requestBytes, "RequestMatchesForTeam", request_matches_for_team.GetRootAsRequestMatchesForTeam)
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800252 if !success {
253 return
254 }
255
256 matches, err := handler.db.QueryMatches(request.Team())
257 if err != nil {
258 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800259 return
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800260 }
261
262 var response RequestAllMatchesResponseT
263 for _, match := range matches {
264 response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
265 MatchNumber: match.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700266 SetNumber: match.SetNumber,
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800267 CompLevel: match.CompLevel,
268 R1: match.R1,
269 R2: match.R2,
270 R3: match.R3,
271 B1: match.B1,
272 B2: match.B2,
273 B3: match.B3,
274 })
275 }
276
277 builder := flatbuffers.NewBuilder(50 * 1024)
278 builder.Finish((&response).Pack(builder))
279 w.Write(builder.FinishedBytes())
280}
281
Philipp Schraderacf96232022-03-01 22:03:30 -0800282// Handles a RequestDataScouting request.
283type requestDataScoutingHandler struct {
284 db Database
285}
286
287func (handler requestDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
288 requestBytes, err := io.ReadAll(req.Body)
289 if err != nil {
290 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
291 return
292 }
293
Philipp Schraderb7e75932022-03-26 16:18:34 -0700294 _, success := parseRequest(w, requestBytes, "RequestDataScouting", request_data_scouting.GetRootAsRequestDataScouting)
Philipp Schraderacf96232022-03-01 22:03:30 -0800295 if !success {
296 return
297 }
298
299 stats, err := handler.db.ReturnStats()
300 if err != nil {
301 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800302 return
Philipp Schraderacf96232022-03-01 22:03:30 -0800303 }
304
305 var response RequestDataScoutingResponseT
306 for _, stat := range stats {
307 response.StatsList = append(response.StatsList, &request_data_scouting_response.StatsT{
Philipp Schraderfa45d742022-03-18 19:29:05 -0700308 Team: stat.TeamNumber,
309 Match: stat.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700310 SetNumber: stat.SetNumber,
Philipp Schrader4535b7e2022-04-08 20:27:00 -0700311 CompLevel: stat.CompLevel,
Philipp Schraderfa45d742022-03-18 19:29:05 -0700312 StartingQuadrant: stat.StartingQuadrant,
313 AutoBall1: stat.AutoBallPickedUp[0],
314 AutoBall2: stat.AutoBallPickedUp[1],
315 AutoBall3: stat.AutoBallPickedUp[2],
316 AutoBall4: stat.AutoBallPickedUp[3],
317 AutoBall5: stat.AutoBallPickedUp[4],
318 MissedShotsAuto: stat.ShotsMissedAuto,
319 UpperGoalAuto: stat.UpperGoalAuto,
320 LowerGoalAuto: stat.LowerGoalAuto,
321 MissedShotsTele: stat.ShotsMissed,
322 UpperGoalTele: stat.UpperGoalShots,
323 LowerGoalTele: stat.LowerGoalShots,
324 DefenseRating: stat.PlayedDefense,
325 DefenseReceivedRating: stat.DefenseReceivedScore,
326 ClimbLevel: request_data_scouting_response.ClimbLevel(stat.Climbing),
327 CollectedBy: stat.CollectedBy,
328 Comment: stat.Comment,
Philipp Schraderacf96232022-03-01 22:03:30 -0800329 })
330 }
331
332 builder := flatbuffers.NewBuilder(50 * 1024)
333 builder.Finish((&response).Pack(builder))
334 w.Write(builder.FinishedBytes())
335}
336
Philipp Schraderd3fac192022-03-02 20:35:46 -0800337func parseTeamKey(teamKey string) (int, error) {
338 // TBA prefixes teams with "frc". Not sure why. Get rid of that.
339 teamKey = strings.TrimPrefix(teamKey, "frc")
340 return strconv.Atoi(teamKey)
341}
342
343// Parses the alliance data from the specified match and returns the three red
344// teams and the three blue teams.
345func parseTeamKeys(match *scraping.Match) ([3]int32, [3]int32, error) {
346 redKeys := match.Alliances.Red.TeamKeys
347 blueKeys := match.Alliances.Blue.TeamKeys
348
349 if len(redKeys) != 3 || len(blueKeys) != 3 {
350 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
351 "Found %d red teams and %d blue teams.", len(redKeys), len(blueKeys)))
352 }
353
354 var red [3]int32
355 for i, key := range redKeys {
356 team, err := parseTeamKey(key)
357 if err != nil {
358 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
359 "Failed to parse red %d team '%s' as integer: %v", i+1, key, err))
360 }
361 red[i] = int32(team)
362 }
363 var blue [3]int32
364 for i, key := range blueKeys {
365 team, err := parseTeamKey(key)
366 if err != nil {
367 return [3]int32{}, [3]int32{}, errors.New(fmt.Sprintf(
368 "Failed to parse blue %d team '%s' as integer: %v", i+1, key, err))
369 }
370 blue[i] = int32(team)
371 }
372 return red, blue, nil
373}
374
375type refreshMatchListHandler struct {
376 db Database
377 scrape ScrapeMatchList
378}
379
380func (handler refreshMatchListHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
381 requestBytes, err := io.ReadAll(req.Body)
382 if err != nil {
383 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
384 return
385 }
386
Philipp Schraderb7e75932022-03-26 16:18:34 -0700387 request, success := parseRequest(w, requestBytes, "RefreshMatchList", refresh_match_list.GetRootAsRefreshMatchList)
Philipp Schraderd3fac192022-03-02 20:35:46 -0800388 if !success {
389 return
390 }
391
392 matches, err := handler.scrape(request.Year(), string(request.EventCode()))
393 if err != nil {
394 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to scrape match list: ", err))
395 return
396 }
397
398 for _, match := range matches {
399 // Make sure the data is valid.
400 red, blue, err := parseTeamKeys(&match)
401 if err != nil {
402 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
403 "TheBlueAlliance data for match %d is malformed: %v", match.MatchNumber, err))
404 return
405 }
406 // Add the match to the database.
Philipp Schrader7365d322022-03-06 16:40:08 -0800407 err = handler.db.AddToMatch(db.Match{
Philipp Schraderd3fac192022-03-02 20:35:46 -0800408 MatchNumber: int32(match.MatchNumber),
Philipp Schrader30b4a682022-04-16 14:36:17 -0700409 SetNumber: int32(match.SetNumber),
Philipp Schrader45befdd2022-04-08 19:12:44 -0700410 CompLevel: match.CompLevel,
411 R1: red[0],
412 R2: red[1],
413 R3: red[2],
414 B1: blue[0],
415 B2: blue[1],
416 B3: blue[2],
Philipp Schraderd3fac192022-03-02 20:35:46 -0800417 })
Philipp Schrader7365d322022-03-06 16:40:08 -0800418 if err != nil {
419 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf(
420 "Failed to add match %d to the database: %v", match.MatchNumber, err))
421 return
422 }
Philipp Schraderd3fac192022-03-02 20:35:46 -0800423 }
424
425 var response RefreshMatchListResponseT
426 builder := flatbuffers.NewBuilder(1024)
427 builder.Finish((&response).Pack(builder))
428 w.Write(builder.FinishedBytes())
429}
430
Alex Perry81f96ba2022-03-13 18:26:19 -0700431type submitNoteScoutingHandler struct {
432 db Database
433}
434
435func (handler submitNoteScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
436 requestBytes, err := io.ReadAll(req.Body)
437 if err != nil {
438 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
439 return
440 }
441
Philipp Schraderb7e75932022-03-26 16:18:34 -0700442 request, success := parseRequest(w, requestBytes, "SubmitNotes", submit_notes.GetRootAsSubmitNotes)
Alex Perry81f96ba2022-03-13 18:26:19 -0700443 if !success {
444 return
445 }
446
447 err = handler.db.AddNotes(db.NotesData{
448 TeamNumber: request.Team(),
449 Notes: []string{string(request.Notes())},
450 })
451 if err != nil {
452 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert notes: %v", err))
453 return
454 }
455
456 var response SubmitNotesResponseT
457 builder := flatbuffers.NewBuilder(10)
458 builder.Finish((&response).Pack(builder))
459 w.Write(builder.FinishedBytes())
460}
461
Alex Perry81f96ba2022-03-13 18:26:19 -0700462type requestNotesForTeamHandler struct {
463 db Database
464}
465
466func (handler requestNotesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
467 requestBytes, err := io.ReadAll(req.Body)
468 if err != nil {
469 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
470 return
471 }
472
Philipp Schraderb7e75932022-03-26 16:18:34 -0700473 request, success := parseRequest(w, requestBytes, "RequestNotesForTeam", request_notes_for_team.GetRootAsRequestNotesForTeam)
Alex Perry81f96ba2022-03-13 18:26:19 -0700474 if !success {
475 return
476 }
477
478 notesData, err := handler.db.QueryNotes(request.Team())
479 if err != nil {
480 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query notes: %v", err))
481 return
482 }
483
484 var response RequestNotesForTeamResponseT
485 for _, data := range notesData.Notes {
486 response.Notes = append(response.Notes, &request_notes_for_team_response.NoteT{data})
487 }
488
489 builder := flatbuffers.NewBuilder(1024)
490 builder.Finish((&response).Pack(builder))
491 w.Write(builder.FinishedBytes())
492}
493
Milo Lin1d59f0c2022-06-22 20:30:58 -0700494type requestShiftScheduleHandler struct {
495 db Database
496}
497
498func (handler requestShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
499 requestBytes, err := io.ReadAll(req.Body)
500 if err != nil {
501 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
502 return
503 }
504
505 _, success := parseRequest(w, requestBytes, "RequestShiftSchedule", request_shift_schedule.GetRootAsRequestShiftSchedule)
506 if !success {
507 return
508 }
509
510 shiftData, err := handler.db.ReturnAllShifts()
511 if err != nil {
512 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query shift schedule: %v", err))
513 return
514 }
515
516 var response RequestShiftScheduleResponseT
517 for _, shifts := range shiftData {
518 response.ShiftSchedule = append(response.ShiftSchedule, &request_shift_schedule_response.MatchAssignmentT{
519 MatchNumber: shifts.MatchNumber,
520 R1scouter: shifts.R1scouter,
521 R2scouter: shifts.R2scouter,
522 R3scouter: shifts.R3scouter,
523 B1scouter: shifts.B1scouter,
524 B2scouter: shifts.B2scouter,
525 B3scouter: shifts.B3scouter,
526 })
527 }
528
529 builder := flatbuffers.NewBuilder(1024)
530 builder.Finish((&response).Pack(builder))
531 w.Write(builder.FinishedBytes())
532}
533
534type submitShiftScheduleHandler struct {
535 db Database
536}
537
538func (handler submitShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
539 // Get the username of the person submitting the data.
540 username := parseUsername(req)
541
542 requestBytes, err := io.ReadAll(req.Body)
543 if err != nil {
544 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
545 return
546 }
547
548 request, success := parseRequest[SubmitShiftSchedule](w, requestBytes, "SubmitShiftSchedule", submit_shift_schedule.GetRootAsSubmitShiftSchedule)
549 if !success {
550 return
551 }
552
553 log.Println("Got shift schedule from", username)
554 shift_schedule_length := request.ShiftScheduleLength()
555 for i := 0; i < shift_schedule_length; i++ {
556 var match_assignment submit_shift_schedule.MatchAssignment
557 request.ShiftSchedule(&match_assignment, i)
558 current_shift := db.Shift{
559 MatchNumber: match_assignment.MatchNumber(),
560 R1scouter: string(match_assignment.R1scouter()),
561 R2scouter: string(match_assignment.R2scouter()),
562 R3scouter: string(match_assignment.R3scouter()),
563 B1scouter: string(match_assignment.B1scouter()),
564 B2scouter: string(match_assignment.B2scouter()),
565 B3scouter: string(match_assignment.B3scouter()),
566 }
567 err = handler.db.AddToShift(current_shift)
568 if err != nil {
569 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit shift schedule: ", err))
570 return
571 }
572 }
573
574 builder := flatbuffers.NewBuilder(50 * 1024)
575 builder.Finish((&SubmitShiftScheduleResponseT{}).Pack(builder))
576 w.Write(builder.FinishedBytes())
577}
578
Philipp Schraderd3fac192022-03-02 20:35:46 -0800579func HandleRequests(db Database, scrape ScrapeMatchList, scoutingServer server.ScoutingServer) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800580 scoutingServer.HandleFunc("/requests", unknown)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800581 scoutingServer.Handle("/requests/submit/data_scouting", submitDataScoutingHandler{db})
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800582 scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800583 scoutingServer.Handle("/requests/request/matches_for_team", requestMatchesForTeamHandler{db})
Philipp Schraderacf96232022-03-01 22:03:30 -0800584 scoutingServer.Handle("/requests/request/data_scouting", requestDataScoutingHandler{db})
Philipp Schraderd3fac192022-03-02 20:35:46 -0800585 scoutingServer.Handle("/requests/refresh_match_list", refreshMatchListHandler{db, scrape})
Alex Perry81f96ba2022-03-13 18:26:19 -0700586 scoutingServer.Handle("/requests/submit/submit_notes", submitNoteScoutingHandler{db})
587 scoutingServer.Handle("/requests/request/notes_for_team", requestNotesForTeamHandler{db})
Milo Lin1d59f0c2022-06-22 20:30:58 -0700588 scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
589 scoutingServer.Handle("/requests/request/shift_schedule", requestShiftScheduleHandler{db})
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800590}