blob: 533959ca08b99e2f4d3ec2d569f3104a57850ffc [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"
Emily Markovabf24c9e2023-02-08 20:31:11 -080010 "sort"
Philipp Schraderd3fac192022-03-02 20:35:46 -080011 "strconv"
12 "strings"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080013
Philipp Schrader8747f1b2022-02-23 23:56:22 -080014 "github.com/frc971/971-Robot-Code/scouting/db"
Filip Kujawac1ded372023-05-27 14:33:43 -070015 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/delete_2023_data_scouting"
16 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/delete_2023_data_scouting_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080017 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
Emily Markova290147d2023-03-03 22:40:06 -080018 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_2023_data_scouting"
19 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_2023_data_scouting_response"
Filip Kujawaf882e022022-12-14 13:14:08 -080020 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_driver_rankings"
21 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_driver_rankings_response"
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080022 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches"
23 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches_response"
Filip Kujawaf882e022022-12-14 13:14:08 -080024 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_notes"
25 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_notes_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070026 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team"
27 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070028 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule"
29 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule_response"
Sabina Leaver759090b2023-01-14 20:42:56 -080030 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_actions"
31 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_actions_response"
Filip Kujawa210a03b2022-11-24 14:41:11 -080032 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking"
33 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070034 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes"
35 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070036 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule"
37 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080038 "github.com/frc971/971-Robot-Code/scouting/webserver/server"
39 flatbuffers "github.com/google/flatbuffers/go"
40)
41
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080042type RequestAllMatches = request_all_matches.RequestAllMatches
43type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
Filip Kujawaf882e022022-12-14 13:14:08 -080044type RequestAllDriverRankings = request_all_driver_rankings.RequestAllDriverRankings
45type RequestAllDriverRankingsResponseT = request_all_driver_rankings_response.RequestAllDriverRankingsResponseT
46type RequestAllNotes = request_all_notes.RequestAllNotes
47type RequestAllNotesResponseT = request_all_notes_response.RequestAllNotesResponseT
Emily Markova290147d2023-03-03 22:40:06 -080048type Request2023DataScouting = request_2023_data_scouting.Request2023DataScouting
49type Request2023DataScoutingResponseT = request_2023_data_scouting_response.Request2023DataScoutingResponseT
Alex Perry81f96ba2022-03-13 18:26:19 -070050type SubmitNotes = submit_notes.SubmitNotes
51type SubmitNotesResponseT = submit_notes_response.SubmitNotesResponseT
52type RequestNotesForTeam = request_notes_for_team.RequestNotesForTeam
53type RequestNotesForTeamResponseT = request_notes_for_team_response.RequestNotesForTeamResponseT
Milo Lin1d59f0c2022-06-22 20:30:58 -070054type RequestShiftSchedule = request_shift_schedule.RequestShiftSchedule
55type RequestShiftScheduleResponseT = request_shift_schedule_response.RequestShiftScheduleResponseT
56type SubmitShiftSchedule = submit_shift_schedule.SubmitShiftSchedule
57type SubmitShiftScheduleResponseT = submit_shift_schedule_response.SubmitShiftScheduleResponseT
Filip Kujawa210a03b2022-11-24 14:41:11 -080058type SubmitDriverRanking = submit_driver_ranking.SubmitDriverRanking
59type SubmitDriverRankingResponseT = submit_driver_ranking_response.SubmitDriverRankingResponseT
Sabina Leaver759090b2023-01-14 20:42:56 -080060type SubmitActions = submit_actions.SubmitActions
Sabina Leaver9b4eb312023-02-20 19:58:17 -080061type Action = submit_actions.Action
Sabina Leaver759090b2023-01-14 20:42:56 -080062type SubmitActionsResponseT = submit_actions_response.SubmitActionsResponseT
Filip Kujawac1ded372023-05-27 14:33:43 -070063type Delete2023DataScouting = delete_2023_data_scouting.Delete2023DataScouting
64type Delete2023DataScoutingResponseT = delete_2023_data_scouting_response.Delete2023DataScoutingResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080065
Philipp Schrader8747f1b2022-02-23 23:56:22 -080066// The interface we expect the database abstraction to conform to.
67// We use an interface here because it makes unit testing easier.
68type Database interface {
Emily Markovabf24c9e2023-02-08 20:31:11 -080069 AddToMatch(db.TeamMatch) error
Milo Lin1d59f0c2022-06-22 20:30:58 -070070 AddToShift(db.Shift) error
Emily Markova290147d2023-03-03 22:40:06 -080071 AddToStats2023(db.Stats2023) error
Emily Markovabf24c9e2023-02-08 20:31:11 -080072 ReturnMatches() ([]db.TeamMatch, error)
Filip Kujawaf882e022022-12-14 13:14:08 -080073 ReturnAllNotes() ([]db.NotesData, error)
74 ReturnAllDriverRankings() ([]db.DriverRankingData, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070075 ReturnAllShifts() ([]db.Shift, error)
Emily Markova290147d2023-03-03 22:40:06 -080076 ReturnStats2023() ([]db.Stats2023, error)
Filip Kujawaf3f9def2023-04-20 13:46:46 -070077 ReturnStats2023ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string, preScouting bool) ([]db.Stats2023, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070078 QueryAllShifts(int) ([]db.Shift, error)
Philipp Schradereecb8962022-06-01 21:02:42 -070079 QueryNotes(int32) ([]string, error)
Filip Kujawaf947cb42022-11-21 10:00:30 -080080 AddNotes(db.NotesData) error
Filip Kujawa210a03b2022-11-24 14:41:11 -080081 AddDriverRanking(db.DriverRankingData) error
Sabina Leaver9b4eb312023-02-20 19:58:17 -080082 AddAction(db.Action) error
Filip Kujawac1ded372023-05-27 14:33:43 -070083 DeleteFromStats(string, int32, int32, string) error
84 DeleteFromActions(string, int32, int32, string) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080085}
86
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080087// Handles unknown requests. Just returns a 404.
88func unknown(w http.ResponseWriter, req *http.Request) {
89 w.WriteHeader(http.StatusNotFound)
90}
91
92func respondWithError(w http.ResponseWriter, statusCode int, errorMessage string) {
93 builder := flatbuffers.NewBuilder(1024)
94 builder.Finish((&error_response.ErrorResponseT{
95 ErrorMessage: errorMessage,
96 }).Pack(builder))
97 w.WriteHeader(statusCode)
98 w.Write(builder.FinishedBytes())
99}
100
101func respondNotImplemented(w http.ResponseWriter) {
102 respondWithError(w, http.StatusNotImplemented, "")
103}
104
Philipp Schraderb7e75932022-03-26 16:18:34 -0700105func 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 -0800106 success := true
107 defer func() {
108 if r := recover(); r != nil {
Philipp Schraderb7e75932022-03-26 16:18:34 -0700109 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse %s: %v", requestName, r))
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800110 success = false
111 }
112 }()
Philipp Schraderb7e75932022-03-26 16:18:34 -0700113 result := parser(buf, 0)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800114 return result, success
115}
116
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700117// Parses the authorization information that the browser inserts into the
118// headers. The authorization follows this format:
119//
Philipp Schrader35bb1532023-03-05 13:49:12 -0800120// req.Headers["Authorization"] = []string{"Basic <base64 encoded username:password>"}
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700121func parseUsername(req *http.Request) string {
122 auth, ok := req.Header["Authorization"]
123 if !ok {
124 return "unknown"
125 }
126
127 parts := strings.Split(auth[0], " ")
128 if !(len(parts) == 2 && parts[0] == "Basic") {
129 return "unknown"
130 }
131
132 info, err := base64.StdEncoding.DecodeString(parts[1])
133 if err != nil {
134 log.Println("ERROR: Failed to parse Basic authentication.")
135 return "unknown"
136 }
137
138 loginParts := strings.Split(string(info), ":")
139 if len(loginParts) != 2 {
140 return "unknown"
141 }
142 return loginParts[0]
143}
144
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800145// Handles a RequestAllMaches request.
146type requestAllMatchesHandler struct {
147 db Database
148}
149
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800150// Change structure of match objects in the database(1 per team) to
151// the old match structure(1 per match) that the webserver uses.
152// We use the information in this struct to identify which match object
153// corresponds to which old match structure object.
154type MatchAssemblyKey struct {
155 MatchNumber int32
156 SetNumber int32
157 CompLevel string
158}
159
Emily Markovabf24c9e2023-02-08 20:31:11 -0800160func findIndexInList(list []string, comp_level string) (int, error) {
161 for index, value := range list {
162 if value == comp_level {
163 return index, nil
164 }
165 }
166 return -1, errors.New(fmt.Sprint("Failed to find comp level ", comp_level, " in list ", list))
167}
168
Emily Markovab8551572023-03-22 19:49:39 -0700169func (handler requestAllMatchesHandler) teamHasBeenDataScouted(key MatchAssemblyKey, teamNumber string) (bool, error) {
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800170 stats, err := handler.db.ReturnStats2023ForTeam(
Filip Kujawaf3f9def2023-04-20 13:46:46 -0700171 teamNumber, key.MatchNumber, key.SetNumber, key.CompLevel, false)
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800172 if err != nil {
173 return false, err
174 }
175 return (len(stats) > 0), nil
176}
177
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800178func (handler requestAllMatchesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
179 requestBytes, err := io.ReadAll(req.Body)
180 if err != nil {
181 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
182 return
183 }
184
Philipp Schraderb7e75932022-03-26 16:18:34 -0700185 _, success := parseRequest(w, requestBytes, "RequestAllMatches", request_all_matches.GetRootAsRequestAllMatches)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800186 if !success {
187 return
188 }
189
190 matches, err := handler.db.ReturnMatches()
191 if err != nil {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700192 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800193 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800194 }
195
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800196 assembledMatches := map[MatchAssemblyKey]request_all_matches_response.MatchT{}
Emily Markovabf24c9e2023-02-08 20:31:11 -0800197
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800198 for _, match := range matches {
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800199 key := MatchAssemblyKey{match.MatchNumber, match.SetNumber, match.CompLevel}
200
201 // Retrieve the converted match structure we have assembled so
202 // far. If we haven't started assembling one yet, then start a
203 // new one.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800204 entry, ok := assembledMatches[key]
205 if !ok {
206 entry = request_all_matches_response.MatchT{
207 MatchNumber: match.MatchNumber,
208 SetNumber: match.SetNumber,
209 CompLevel: match.CompLevel,
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800210 DataScouted: &request_all_matches_response.ScoutedLevelT{},
Emily Markovabf24c9e2023-02-08 20:31:11 -0800211 }
212 }
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800213
Emily Markovab8551572023-03-22 19:49:39 -0700214 var team *string
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800215 var dataScoutedTeam *bool
216
217 // Fill in the field for the match that we have in in the
218 // database. In the database, each match row only has 1 team
219 // number.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800220 switch match.Alliance {
221 case "R":
222 switch match.AlliancePosition {
223 case 1:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800224 team = &entry.R1
225 dataScoutedTeam = &entry.DataScouted.R1
Emily Markovabf24c9e2023-02-08 20:31:11 -0800226 case 2:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800227 team = &entry.R2
228 dataScoutedTeam = &entry.DataScouted.R2
Emily Markovabf24c9e2023-02-08 20:31:11 -0800229 case 3:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800230 team = &entry.R3
231 dataScoutedTeam = &entry.DataScouted.R3
Emily Markovabf24c9e2023-02-08 20:31:11 -0800232 default:
233 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown red position ", strconv.Itoa(int(match.AlliancePosition)), " in match ", strconv.Itoa(int(match.MatchNumber))))
234 return
235 }
236 case "B":
237 switch match.AlliancePosition {
238 case 1:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800239 team = &entry.B1
240 dataScoutedTeam = &entry.DataScouted.B1
Emily Markovabf24c9e2023-02-08 20:31:11 -0800241 case 2:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800242 team = &entry.B2
243 dataScoutedTeam = &entry.DataScouted.B2
Emily Markovabf24c9e2023-02-08 20:31:11 -0800244 case 3:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800245 team = &entry.B3
246 dataScoutedTeam = &entry.DataScouted.B3
Emily Markovabf24c9e2023-02-08 20:31:11 -0800247 default:
248 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown blue position ", strconv.Itoa(int(match.AlliancePosition)), " in match ", strconv.Itoa(int(match.MatchNumber))))
249 return
250 }
251 default:
252 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown alliance ", match.Alliance, " in match ", strconv.Itoa(int(match.AlliancePosition))))
253 return
254 }
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800255
256 *team = match.TeamNumber
257
258 // Figure out if this team has been data scouted already.
259 *dataScoutedTeam, err = handler.teamHasBeenDataScouted(key, match.TeamNumber)
260 if err != nil {
261 respondWithError(w, http.StatusInternalServerError, fmt.Sprint(
262 "Failed to determine data scouting status for team ",
263 strconv.Itoa(int(match.AlliancePosition)),
264 " in match ",
265 strconv.Itoa(int(match.MatchNumber)),
266 err))
267 return
268 }
269
Emily Markovabf24c9e2023-02-08 20:31:11 -0800270 assembledMatches[key] = entry
271 }
272
273 var response RequestAllMatchesResponseT
274 for _, match := range assembledMatches {
275 copied_match := match
276 response.MatchList = append(response.MatchList, &copied_match)
277 }
278
279 var MATCH_TYPE_ORDERING = []string{"qm", "ef", "qf", "sf", "f"}
280
281 err = nil
282 sort.Slice(response.MatchList, func(i, j int) bool {
283 if err != nil {
284 return false
285 }
286 a := response.MatchList[i]
287 b := response.MatchList[j]
288
Emily Markovaabcac6e2023-02-18 17:50:03 -0800289 aMatchTypeIndex, err2 := findIndexInList(MATCH_TYPE_ORDERING, a.CompLevel)
290 if err2 != nil {
291 err = errors.New(fmt.Sprint("Comp level ", a.CompLevel, " not found in sorting list ", MATCH_TYPE_ORDERING, " : ", err2))
Emily Markovabf24c9e2023-02-08 20:31:11 -0800292 return false
293 }
Emily Markovaabcac6e2023-02-18 17:50:03 -0800294 bMatchTypeIndex, err2 := findIndexInList(MATCH_TYPE_ORDERING, b.CompLevel)
295 if err2 != nil {
296 err = errors.New(fmt.Sprint("Comp level ", b.CompLevel, " not found in sorting list ", MATCH_TYPE_ORDERING, " : ", err2))
Emily Markovabf24c9e2023-02-08 20:31:11 -0800297 return false
298 }
299
300 if aMatchTypeIndex < bMatchTypeIndex {
301 return true
302 }
303 if aMatchTypeIndex > bMatchTypeIndex {
304 return false
305 }
306
307 // Then sort by match number. E.g. in semi finals, all match 1 rounds
308 // are done first. Then come match 2 rounds. And then, if necessary,
309 // the match 3 rounds.
310 aMatchNumber := a.MatchNumber
311 bMatchNumber := b.MatchNumber
312 if aMatchNumber < bMatchNumber {
313 return true
314 }
315 if aMatchNumber > bMatchNumber {
316 return false
317 }
318 // Lastly, sort by set number. I.e. Semi Final 1 Match 1 happens first.
319 // Then comes Semi Final 2 Match 1. Then comes Semi Final 1 Match 2. Then
320 // Semi Final 2 Match 2.
321 aSetNumber := a.SetNumber
322 bSetNumber := b.SetNumber
323 if aSetNumber < bSetNumber {
324 return true
325 }
326 if aSetNumber > bSetNumber {
327 return false
328 }
329 return true
330 })
331
332 if err != nil {
333 // check if error happened during sorting and notify webpage if that
334 respondWithError(w, http.StatusInternalServerError, fmt.Sprint(err))
335 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800336 }
337
338 builder := flatbuffers.NewBuilder(50 * 1024)
339 builder.Finish((&response).Pack(builder))
340 w.Write(builder.FinishedBytes())
341}
342
Alex Perry81f96ba2022-03-13 18:26:19 -0700343type submitNoteScoutingHandler struct {
344 db Database
345}
346
347func (handler submitNoteScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
348 requestBytes, err := io.ReadAll(req.Body)
349 if err != nil {
350 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
351 return
352 }
353
Philipp Schraderb7e75932022-03-26 16:18:34 -0700354 request, success := parseRequest(w, requestBytes, "SubmitNotes", submit_notes.GetRootAsSubmitNotes)
Alex Perry81f96ba2022-03-13 18:26:19 -0700355 if !success {
356 return
357 }
358
Filip Kujawaf947cb42022-11-21 10:00:30 -0800359 err = handler.db.AddNotes(db.NotesData{
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800360 TeamNumber: request.Team(),
361 Notes: string(request.Notes()),
362 GoodDriving: bool(request.GoodDriving()),
363 BadDriving: bool(request.BadDriving()),
Filip Kujawa11dc4c92023-04-13 08:55:43 -0700364 SolidPlacing: bool(request.SolidPlacing()),
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800365 SketchyPlacing: bool(request.SketchyPlacing()),
366 GoodDefense: bool(request.GoodDefense()),
367 BadDefense: bool(request.BadDefense()),
368 EasilyDefended: bool(request.EasilyDefended()),
Filip Kujawaf947cb42022-11-21 10:00:30 -0800369 })
Alex Perry81f96ba2022-03-13 18:26:19 -0700370 if err != nil {
371 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert notes: %v", err))
372 return
373 }
374
375 var response SubmitNotesResponseT
376 builder := flatbuffers.NewBuilder(10)
377 builder.Finish((&response).Pack(builder))
378 w.Write(builder.FinishedBytes())
379}
380
Emily Markova1abe9782023-03-11 19:45:38 -0800381func ConvertActionsToStat(submitActions *submit_actions.SubmitActions) (db.Stats2023, error) {
382 overall_time := int64(0)
383 cycles := int64(0)
384 picked_up := false
385 lastPlacedTime := int64(0)
Philipp Schrader4b489222023-04-15 16:40:16 -0700386 stat := db.Stats2023{
387 PreScouting: submitActions.PreScouting(),
388 TeamNumber: string(submitActions.TeamNumber()), MatchNumber: submitActions.MatchNumber(), SetNumber: submitActions.SetNumber(), CompLevel: string(submitActions.CompLevel()),
Emily Markova1abe9782023-03-11 19:45:38 -0800389 StartingQuadrant: 0, LowCubesAuto: 0, MiddleCubesAuto: 0, HighCubesAuto: 0, CubesDroppedAuto: 0,
390 LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 0, ConesDroppedAuto: 0, LowCubes: 0, MiddleCubes: 0, HighCubes: 0,
Philipp Schradere11114f2023-04-15 17:04:25 -0700391 CubesDropped: 0, LowCones: 0, MiddleCones: 0, HighCones: 0, ConesDropped: 0, SuperchargedPieces: 0, AvgCycle: 0, CollectedBy: "",
Emily Markova1abe9782023-03-11 19:45:38 -0800392 }
393 // Loop over all actions.
394 for i := 0; i < submitActions.ActionsListLength(); i++ {
395 var action submit_actions.Action
396 if !submitActions.ActionsList(&action, i) {
397 return db.Stats2023{}, errors.New(fmt.Sprintf("Failed to parse submit_actions.Action"))
398 }
399 actionTable := new(flatbuffers.Table)
400 action_type := action.ActionTakenType()
401 if !action.ActionTaken(actionTable) {
402 return db.Stats2023{}, errors.New(fmt.Sprint("Failed to parse sub-action or sub-action was missing"))
403 }
404 if action_type == submit_actions.ActionTypeStartMatchAction {
405 var startMatchAction submit_actions.StartMatchAction
406 startMatchAction.Init(actionTable.Bytes, actionTable.Pos)
407 stat.StartingQuadrant = startMatchAction.Position()
Filip Kujawa0b4b1e52023-04-15 14:05:40 -0700408 } else if action_type == submit_actions.ActionTypeMobilityAction {
409 var mobilityAction submit_actions.MobilityAction
410 mobilityAction.Init(actionTable.Bytes, actionTable.Pos)
411 if mobilityAction.Mobility() {
412 stat.Mobility = true
413 }
414
Emily Markova46a69bf2023-03-22 20:45:52 -0700415 } else if action_type == submit_actions.ActionTypeAutoBalanceAction {
416 var autoBalanceAction submit_actions.AutoBalanceAction
417 autoBalanceAction.Init(actionTable.Bytes, actionTable.Pos)
418 if autoBalanceAction.Docked() {
419 stat.DockedAuto = true
420 }
421 if autoBalanceAction.Engaged() {
422 stat.EngagedAuto = true
423 }
Emily Markova63c63f62023-03-29 20:57:35 -0700424 if autoBalanceAction.BalanceAttempt() {
425 stat.BalanceAttemptAuto = true
426 }
Emily Markova1abe9782023-03-11 19:45:38 -0800427 } else if action_type == submit_actions.ActionTypePickupObjectAction {
428 var pick_up_action submit_actions.PickupObjectAction
429 pick_up_action.Init(actionTable.Bytes, actionTable.Pos)
430 if picked_up == true {
431 object := pick_up_action.ObjectType().String()
432 auto := pick_up_action.Auto()
433 if object == "kCube" && auto == false {
434 stat.CubesDropped += 1
435 } else if object == "kCube" && auto == true {
436 stat.CubesDroppedAuto += 1
437 } else if object == "kCone" && auto == false {
438 stat.ConesDropped += 1
439 } else if object == "kCube" && auto == true {
440 stat.ConesDroppedAuto += 1
441 }
442 } else {
443 picked_up = true
444 }
445 } else if action_type == submit_actions.ActionTypePlaceObjectAction {
446 var place_action submit_actions.PlaceObjectAction
447 place_action.Init(actionTable.Bytes, actionTable.Pos)
448 if !picked_up {
449 return db.Stats2023{}, errors.New(fmt.Sprintf("Got PlaceObjectAction without corresponding PickupObjectAction"))
450 }
451 object := place_action.ObjectType()
452 level := place_action.ScoreLevel()
453 auto := place_action.Auto()
454 if object == 0 && level == 0 && auto == true {
455 stat.LowCubesAuto += 1
456 } else if object == 0 && level == 0 && auto == false {
457 stat.LowCubes += 1
458 } else if object == 0 && level == 1 && auto == true {
459 stat.MiddleCubesAuto += 1
460 } else if object == 0 && level == 1 && auto == false {
461 stat.MiddleCubes += 1
462 } else if object == 0 && level == 2 && auto == true {
463 stat.HighCubesAuto += 1
464 } else if object == 0 && level == 2 && auto == false {
465 stat.HighCubes += 1
466 } else if object == 1 && level == 0 && auto == true {
467 stat.LowConesAuto += 1
468 } else if object == 1 && level == 0 && auto == false {
469 stat.LowCones += 1
470 } else if object == 1 && level == 1 && auto == true {
471 stat.MiddleConesAuto += 1
472 } else if object == 1 && level == 1 && auto == false {
473 stat.MiddleCones += 1
474 } else if object == 1 && level == 2 && auto == true {
475 stat.HighConesAuto += 1
476 } else if object == 1 && level == 2 && auto == false {
477 stat.HighCones += 1
Filip Kujawa7a045e72023-04-13 08:41:09 -0700478 } else if level == 3 {
479 stat.SuperchargedPieces += 1
Emily Markova1abe9782023-03-11 19:45:38 -0800480 } else {
481 return db.Stats2023{}, errors.New(fmt.Sprintf("Got unknown ObjectType/ScoreLevel/Auto combination"))
482 }
483 picked_up = false
484 if lastPlacedTime != int64(0) {
485 // If this is not the first time we place,
486 // start counting cycle time. We define cycle
487 // time as the time between placements.
488 overall_time += int64(action.Timestamp()) - lastPlacedTime
489 cycles += 1
490 }
491 lastPlacedTime = int64(action.Timestamp())
Emily Markova46a69bf2023-03-22 20:45:52 -0700492 } else if action_type == submit_actions.ActionTypeEndMatchAction {
493 var endMatchAction submit_actions.EndMatchAction
494 endMatchAction.Init(actionTable.Bytes, actionTable.Pos)
495 if endMatchAction.Docked() {
496 stat.Docked = true
497 }
498 if endMatchAction.Engaged() {
499 stat.Engaged = true
500 }
Emily Markova63c63f62023-03-29 20:57:35 -0700501 if endMatchAction.BalanceAttempt() {
502 stat.BalanceAttempt = true
503 }
Emily Markova1abe9782023-03-11 19:45:38 -0800504 }
505 }
506 if cycles != 0 {
Philipp Schrader8c878a22023-03-20 22:36:38 -0700507 stat.AvgCycle = overall_time / cycles
Emily Markova1abe9782023-03-11 19:45:38 -0800508 } else {
509 stat.AvgCycle = 0
510 }
511 return stat, nil
512}
513
Emily Markova290147d2023-03-03 22:40:06 -0800514// Handles a Request2023DataScouting request.
515type request2023DataScoutingHandler struct {
516 db Database
517}
518
519func (handler request2023DataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
520 requestBytes, err := io.ReadAll(req.Body)
521 if err != nil {
522 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
523 return
524 }
525
526 _, success := parseRequest(w, requestBytes, "Request2023DataScouting", request_2023_data_scouting.GetRootAsRequest2023DataScouting)
527 if !success {
528 return
529 }
530
531 stats, err := handler.db.ReturnStats2023()
532 if err != nil {
533 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
534 return
535 }
536
537 var response Request2023DataScoutingResponseT
538 for _, stat := range stats {
539 response.StatsList = append(response.StatsList, &request_2023_data_scouting_response.Stats2023T{
Emily Markova63c63f62023-03-29 20:57:35 -0700540 TeamNumber: stat.TeamNumber,
541 MatchNumber: stat.MatchNumber,
542 SetNumber: stat.SetNumber,
543 CompLevel: stat.CompLevel,
544 StartingQuadrant: stat.StartingQuadrant,
545 LowCubesAuto: stat.LowCubesAuto,
546 MiddleCubesAuto: stat.MiddleCubesAuto,
547 HighCubesAuto: stat.HighCubesAuto,
548 CubesDroppedAuto: stat.CubesDroppedAuto,
549 LowConesAuto: stat.LowConesAuto,
550 MiddleConesAuto: stat.MiddleConesAuto,
551 HighConesAuto: stat.HighConesAuto,
552 ConesDroppedAuto: stat.ConesDroppedAuto,
553 LowCubes: stat.LowCubes,
554 MiddleCubes: stat.MiddleCubes,
555 HighCubes: stat.HighCubes,
556 CubesDropped: stat.CubesDropped,
557 LowCones: stat.LowCones,
558 MiddleCones: stat.MiddleCones,
559 HighCones: stat.HighCones,
560 ConesDropped: stat.ConesDropped,
Filip Kujawa7a045e72023-04-13 08:41:09 -0700561 SuperchargedPieces: stat.SuperchargedPieces,
Emily Markova63c63f62023-03-29 20:57:35 -0700562 AvgCycle: stat.AvgCycle,
Filip Kujawa0b4b1e52023-04-15 14:05:40 -0700563 Mobility: stat.Mobility,
Emily Markova63c63f62023-03-29 20:57:35 -0700564 DockedAuto: stat.DockedAuto,
565 EngagedAuto: stat.EngagedAuto,
566 BalanceAttemptAuto: stat.BalanceAttemptAuto,
567 Docked: stat.Docked,
568 Engaged: stat.Engaged,
569 BalanceAttempt: stat.BalanceAttempt,
570 CollectedBy: stat.CollectedBy,
Emily Markova290147d2023-03-03 22:40:06 -0800571 })
572 }
573
574 builder := flatbuffers.NewBuilder(50 * 1024)
575 builder.Finish((&response).Pack(builder))
576 w.Write(builder.FinishedBytes())
577}
578
Alex Perry81f96ba2022-03-13 18:26:19 -0700579type requestNotesForTeamHandler struct {
580 db Database
581}
582
583func (handler requestNotesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
584 requestBytes, err := io.ReadAll(req.Body)
585 if err != nil {
586 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
587 return
588 }
589
Philipp Schraderb7e75932022-03-26 16:18:34 -0700590 request, success := parseRequest(w, requestBytes, "RequestNotesForTeam", request_notes_for_team.GetRootAsRequestNotesForTeam)
Alex Perry81f96ba2022-03-13 18:26:19 -0700591 if !success {
592 return
593 }
594
Philipp Schradereecb8962022-06-01 21:02:42 -0700595 notes, err := handler.db.QueryNotes(request.Team())
Alex Perry81f96ba2022-03-13 18:26:19 -0700596 if err != nil {
597 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query notes: %v", err))
598 return
599 }
600
601 var response RequestNotesForTeamResponseT
Philipp Schradereecb8962022-06-01 21:02:42 -0700602 for _, data := range notes {
Alex Perry81f96ba2022-03-13 18:26:19 -0700603 response.Notes = append(response.Notes, &request_notes_for_team_response.NoteT{data})
604 }
605
606 builder := flatbuffers.NewBuilder(1024)
607 builder.Finish((&response).Pack(builder))
608 w.Write(builder.FinishedBytes())
609}
610
Milo Lin1d59f0c2022-06-22 20:30:58 -0700611type requestShiftScheduleHandler struct {
612 db Database
613}
614
615func (handler requestShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
616 requestBytes, err := io.ReadAll(req.Body)
617 if err != nil {
618 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
619 return
620 }
621
622 _, success := parseRequest(w, requestBytes, "RequestShiftSchedule", request_shift_schedule.GetRootAsRequestShiftSchedule)
623 if !success {
624 return
625 }
626
627 shiftData, err := handler.db.ReturnAllShifts()
628 if err != nil {
629 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query shift schedule: %v", err))
630 return
631 }
632
633 var response RequestShiftScheduleResponseT
634 for _, shifts := range shiftData {
635 response.ShiftSchedule = append(response.ShiftSchedule, &request_shift_schedule_response.MatchAssignmentT{
636 MatchNumber: shifts.MatchNumber,
Philipp Schrader2ff455b2023-05-03 22:11:50 -0700637 R1Scouter: shifts.R1scouter,
638 R2Scouter: shifts.R2scouter,
639 R3Scouter: shifts.R3scouter,
640 B1Scouter: shifts.B1scouter,
641 B2Scouter: shifts.B2scouter,
642 B3Scouter: shifts.B3scouter,
Milo Lin1d59f0c2022-06-22 20:30:58 -0700643 })
644 }
645
646 builder := flatbuffers.NewBuilder(1024)
647 builder.Finish((&response).Pack(builder))
648 w.Write(builder.FinishedBytes())
649}
650
651type submitShiftScheduleHandler struct {
652 db Database
653}
654
655func (handler submitShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
656 // Get the username of the person submitting the data.
657 username := parseUsername(req)
658
659 requestBytes, err := io.ReadAll(req.Body)
660 if err != nil {
661 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
662 return
663 }
664
665 request, success := parseRequest[SubmitShiftSchedule](w, requestBytes, "SubmitShiftSchedule", submit_shift_schedule.GetRootAsSubmitShiftSchedule)
666 if !success {
667 return
668 }
669
670 log.Println("Got shift schedule from", username)
671 shift_schedule_length := request.ShiftScheduleLength()
672 for i := 0; i < shift_schedule_length; i++ {
673 var match_assignment submit_shift_schedule.MatchAssignment
674 request.ShiftSchedule(&match_assignment, i)
675 current_shift := db.Shift{
676 MatchNumber: match_assignment.MatchNumber(),
Philipp Schrader2ff455b2023-05-03 22:11:50 -0700677 R1scouter: string(match_assignment.R1Scouter()),
678 R2scouter: string(match_assignment.R2Scouter()),
679 R3scouter: string(match_assignment.R3Scouter()),
680 B1scouter: string(match_assignment.B1Scouter()),
681 B2scouter: string(match_assignment.B2Scouter()),
682 B3scouter: string(match_assignment.B3Scouter()),
Milo Lin1d59f0c2022-06-22 20:30:58 -0700683 }
684 err = handler.db.AddToShift(current_shift)
685 if err != nil {
686 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit shift schedule: ", err))
687 return
688 }
689 }
690
691 builder := flatbuffers.NewBuilder(50 * 1024)
692 builder.Finish((&SubmitShiftScheduleResponseT{}).Pack(builder))
693 w.Write(builder.FinishedBytes())
694}
695
Filip Kujawa210a03b2022-11-24 14:41:11 -0800696type SubmitDriverRankingHandler struct {
697 db Database
698}
699
700func (handler SubmitDriverRankingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
701 requestBytes, err := io.ReadAll(req.Body)
702 if err != nil {
703 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
704 return
705 }
706
707 request, success := parseRequest(w, requestBytes, "SubmitDriverRanking", submit_driver_ranking.GetRootAsSubmitDriverRanking)
708 if !success {
709 return
710 }
711
712 err = handler.db.AddDriverRanking(db.DriverRankingData{
713 MatchNumber: request.MatchNumber(),
714 Rank1: request.Rank1(),
715 Rank2: request.Rank2(),
716 Rank3: request.Rank3(),
717 })
718
719 if err != nil {
720 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert driver ranking: %v", err))
721 return
722 }
723
724 var response SubmitDriverRankingResponseT
725 builder := flatbuffers.NewBuilder(10)
726 builder.Finish((&response).Pack(builder))
727 w.Write(builder.FinishedBytes())
728}
729
Filip Kujawaf882e022022-12-14 13:14:08 -0800730type requestAllNotesHandler struct {
731 db Database
732}
733
734func (handler requestAllNotesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
735 requestBytes, err := io.ReadAll(req.Body)
736 if err != nil {
737 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
738 return
739 }
740
741 _, success := parseRequest(w, requestBytes, "RequestAllNotes", request_all_notes.GetRootAsRequestAllNotes)
742 if !success {
743 return
744 }
745
746 notes, err := handler.db.ReturnAllNotes()
747 if err != nil {
748 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
749 return
750 }
751
752 var response RequestAllNotesResponseT
753 for _, note := range notes {
754 response.NoteList = append(response.NoteList, &request_all_notes_response.NoteT{
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800755 Team: note.TeamNumber,
756 Notes: note.Notes,
757 GoodDriving: note.GoodDriving,
758 BadDriving: note.BadDriving,
Filip Kujawa11dc4c92023-04-13 08:55:43 -0700759 SolidPlacing: note.SolidPlacing,
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800760 SketchyPlacing: note.SketchyPlacing,
761 GoodDefense: note.GoodDefense,
762 BadDefense: note.BadDefense,
763 EasilyDefended: note.EasilyDefended,
Filip Kujawaf882e022022-12-14 13:14:08 -0800764 })
765 }
766
767 builder := flatbuffers.NewBuilder(50 * 1024)
768 builder.Finish((&response).Pack(builder))
769 w.Write(builder.FinishedBytes())
770}
771
772type requestAllDriverRankingsHandler struct {
773 db Database
774}
775
776func (handler requestAllDriverRankingsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
777 requestBytes, err := io.ReadAll(req.Body)
778 if err != nil {
779 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
780 return
781 }
782
783 _, success := parseRequest(w, requestBytes, "RequestAllDriverRankings", request_all_driver_rankings.GetRootAsRequestAllDriverRankings)
784 if !success {
785 return
786 }
787
788 rankings, err := handler.db.ReturnAllDriverRankings()
789 if err != nil {
790 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
791 return
792 }
793
794 var response RequestAllDriverRankingsResponseT
795 for _, ranking := range rankings {
796 response.DriverRankingList = append(response.DriverRankingList, &request_all_driver_rankings_response.RankingT{
797 MatchNumber: ranking.MatchNumber,
798 Rank1: ranking.Rank1,
799 Rank2: ranking.Rank2,
800 Rank3: ranking.Rank3,
801 })
802 }
803
804 builder := flatbuffers.NewBuilder(50 * 1024)
805 builder.Finish((&response).Pack(builder))
806 w.Write(builder.FinishedBytes())
807}
808
Sabina Leaver9b4eb312023-02-20 19:58:17 -0800809type submitActionsHandler struct {
810 db Database
811}
812
813func (handler submitActionsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
814 // Get the username of the person submitting the data.
815 username := parseUsername(req)
816
817 requestBytes, err := io.ReadAll(req.Body)
818 if err != nil {
819 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
820 return
821 }
822
823 request, success := parseRequest(w, requestBytes, "SubmitActions", submit_actions.GetRootAsSubmitActions)
824 if !success {
825 return
826 }
827
828 log.Println("Got actions for match", request.MatchNumber(), "team", request.TeamNumber(), "from", username)
829
830 for i := 0; i < request.ActionsListLength(); i++ {
831
832 var action Action
833 request.ActionsList(&action, i)
834
835 dbAction := db.Action{
Philipp Schrader4b489222023-04-15 16:40:16 -0700836 PreScouting: request.PreScouting(),
Sabina Leaver9b4eb312023-02-20 19:58:17 -0800837 TeamNumber: string(request.TeamNumber()),
838 MatchNumber: request.MatchNumber(),
839 SetNumber: request.SetNumber(),
840 CompLevel: string(request.CompLevel()),
841 //TODO: Serialize CompletedAction
842 CompletedAction: []byte{},
Philipp Schrader670a1c82023-05-17 19:42:43 -0700843 Timestamp: action.Timestamp(),
Sabina Leaver9b4eb312023-02-20 19:58:17 -0800844 CollectedBy: username,
845 }
846
847 // Do some error checking.
848 if action.Timestamp() < 0 {
849 respondWithError(w, http.StatusBadRequest, fmt.Sprint(
850 "Invalid timestamp field value of ", action.Timestamp()))
851 return
852 }
853
854 err = handler.db.AddAction(dbAction)
855 if err != nil {
856 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to add action to database: ", err))
857 return
858 }
859 }
860
Philipp Schradere11114f2023-04-15 17:04:25 -0700861 stats, err := ConvertActionsToStat(request)
862 if err != nil {
863 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to convert actions to stats: ", err))
864 return
865 }
866
867 stats.CollectedBy = username
868
869 err = handler.db.AddToStats2023(stats)
870 if err != nil {
871 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit stats: ", stats, ": ", err))
872 return
873 }
874
Sabina Leaver9b4eb312023-02-20 19:58:17 -0800875 builder := flatbuffers.NewBuilder(50 * 1024)
876 builder.Finish((&SubmitActionsResponseT{}).Pack(builder))
877 w.Write(builder.FinishedBytes())
878}
879
Filip Kujawac1ded372023-05-27 14:33:43 -0700880type Delete2023DataScoutingHandler struct {
881 db Database
882}
883
884func (handler Delete2023DataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
885 requestBytes, err := io.ReadAll(req.Body)
886 if err != nil {
887 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
888 return
889 }
890
891 request, success := parseRequest(w, requestBytes, "Delete2023DataScouting", delete_2023_data_scouting.GetRootAsDelete2023DataScouting)
892 if !success {
893 return
894 }
895
896 err = handler.db.DeleteFromStats(
897 string(request.CompLevel()),
898 request.MatchNumber(),
899 request.SetNumber(),
900 string(request.TeamNumber()))
901
902 if err != nil {
903 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to delete from stats: %v", err))
904 return
905 }
906
907 err = handler.db.DeleteFromActions(
908 string(request.CompLevel()),
909 request.MatchNumber(),
910 request.SetNumber(),
911 string(request.TeamNumber()))
912
913 if err != nil {
914 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to delete from actions: %v", err))
915 return
916 }
917
918 var response Delete2023DataScoutingResponseT
919 builder := flatbuffers.NewBuilder(10)
920 builder.Finish((&response).Pack(builder))
921 w.Write(builder.FinishedBytes())
922}
923
Philipp Schrader43c730b2023-02-26 20:27:44 -0800924func HandleRequests(db Database, scoutingServer server.ScoutingServer) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800925 scoutingServer.HandleFunc("/requests", unknown)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800926 scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
Filip Kujawaf882e022022-12-14 13:14:08 -0800927 scoutingServer.Handle("/requests/request/all_notes", requestAllNotesHandler{db})
928 scoutingServer.Handle("/requests/request/all_driver_rankings", requestAllDriverRankingsHandler{db})
Emily Markova290147d2023-03-03 22:40:06 -0800929 scoutingServer.Handle("/requests/request/2023_data_scouting", request2023DataScoutingHandler{db})
Alex Perry81f96ba2022-03-13 18:26:19 -0700930 scoutingServer.Handle("/requests/submit/submit_notes", submitNoteScoutingHandler{db})
931 scoutingServer.Handle("/requests/request/notes_for_team", requestNotesForTeamHandler{db})
Milo Lin1d59f0c2022-06-22 20:30:58 -0700932 scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
933 scoutingServer.Handle("/requests/request/shift_schedule", requestShiftScheduleHandler{db})
Filip Kujawa210a03b2022-11-24 14:41:11 -0800934 scoutingServer.Handle("/requests/submit/submit_driver_ranking", SubmitDriverRankingHandler{db})
Sabina Leaver9b4eb312023-02-20 19:58:17 -0800935 scoutingServer.Handle("/requests/submit/submit_actions", submitActionsHandler{db})
Filip Kujawac1ded372023-05-27 14:33:43 -0700936 scoutingServer.Handle("/requests/delete/delete_2023_data_scouting", Delete2023DataScoutingHandler{db})
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800937}