blob: bb40ecf8eb85845dff705b98007c3b7e8f4e4c82 [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"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080015 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
Emily Markova290147d2023-03-03 22:40:06 -080016 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_2023_data_scouting"
17 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_2023_data_scouting_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"
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"
Sabina Leaver759090b2023-01-14 20:42:56 -080028 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_actions"
29 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_actions_response"
Filip Kujawa210a03b2022-11-24 14:41:11 -080030 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking"
31 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070032 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes"
33 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070034 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule"
35 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080036 "github.com/frc971/971-Robot-Code/scouting/webserver/server"
37 flatbuffers "github.com/google/flatbuffers/go"
38)
39
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080040type RequestAllMatches = request_all_matches.RequestAllMatches
41type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
Filip Kujawaf882e022022-12-14 13:14:08 -080042type RequestAllDriverRankings = request_all_driver_rankings.RequestAllDriverRankings
43type RequestAllDriverRankingsResponseT = request_all_driver_rankings_response.RequestAllDriverRankingsResponseT
44type RequestAllNotes = request_all_notes.RequestAllNotes
45type RequestAllNotesResponseT = request_all_notes_response.RequestAllNotesResponseT
Emily Markova290147d2023-03-03 22:40:06 -080046type Request2023DataScouting = request_2023_data_scouting.Request2023DataScouting
47type Request2023DataScoutingResponseT = request_2023_data_scouting_response.Request2023DataScoutingResponseT
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
Filip Kujawa210a03b2022-11-24 14:41:11 -080056type SubmitDriverRanking = submit_driver_ranking.SubmitDriverRanking
57type SubmitDriverRankingResponseT = submit_driver_ranking_response.SubmitDriverRankingResponseT
Sabina Leaver759090b2023-01-14 20:42:56 -080058type SubmitActions = submit_actions.SubmitActions
59type SubmitActionsResponseT = submit_actions_response.SubmitActionsResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080060
Philipp Schrader8747f1b2022-02-23 23:56:22 -080061// The interface we expect the database abstraction to conform to.
62// We use an interface here because it makes unit testing easier.
63type Database interface {
Emily Markovabf24c9e2023-02-08 20:31:11 -080064 AddToMatch(db.TeamMatch) error
Milo Lin1d59f0c2022-06-22 20:30:58 -070065 AddToShift(db.Shift) error
Emily Markova290147d2023-03-03 22:40:06 -080066 AddToStats2023(db.Stats2023) error
Emily Markovabf24c9e2023-02-08 20:31:11 -080067 ReturnMatches() ([]db.TeamMatch, error)
Filip Kujawaf882e022022-12-14 13:14:08 -080068 ReturnAllNotes() ([]db.NotesData, error)
69 ReturnAllDriverRankings() ([]db.DriverRankingData, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070070 ReturnAllShifts() ([]db.Shift, error)
Emily Markova290147d2023-03-03 22:40:06 -080071 ReturnStats2023() ([]db.Stats2023, error)
Philipp Schrader0f7b6362023-03-11 14:02:48 -080072 ReturnStats2023ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string) ([]db.Stats2023, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070073 QueryAllShifts(int) ([]db.Shift, error)
Philipp Schradereecb8962022-06-01 21:02:42 -070074 QueryNotes(int32) ([]string, error)
Filip Kujawaf947cb42022-11-21 10:00:30 -080075 AddNotes(db.NotesData) error
Filip Kujawa210a03b2022-11-24 14:41:11 -080076 AddDriverRanking(db.DriverRankingData) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080077}
78
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080079// Handles unknown requests. Just returns a 404.
80func unknown(w http.ResponseWriter, req *http.Request) {
81 w.WriteHeader(http.StatusNotFound)
82}
83
84func respondWithError(w http.ResponseWriter, statusCode int, errorMessage string) {
85 builder := flatbuffers.NewBuilder(1024)
86 builder.Finish((&error_response.ErrorResponseT{
87 ErrorMessage: errorMessage,
88 }).Pack(builder))
89 w.WriteHeader(statusCode)
90 w.Write(builder.FinishedBytes())
91}
92
93func respondNotImplemented(w http.ResponseWriter) {
94 respondWithError(w, http.StatusNotImplemented, "")
95}
96
Philipp Schraderb7e75932022-03-26 16:18:34 -070097func 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 -080098 success := true
99 defer func() {
100 if r := recover(); r != nil {
Philipp Schraderb7e75932022-03-26 16:18:34 -0700101 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse %s: %v", requestName, r))
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800102 success = false
103 }
104 }()
Philipp Schraderb7e75932022-03-26 16:18:34 -0700105 result := parser(buf, 0)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800106 return result, success
107}
108
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700109// Parses the authorization information that the browser inserts into the
110// headers. The authorization follows this format:
111//
Philipp Schrader35bb1532023-03-05 13:49:12 -0800112// req.Headers["Authorization"] = []string{"Basic <base64 encoded username:password>"}
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700113func parseUsername(req *http.Request) string {
114 auth, ok := req.Header["Authorization"]
115 if !ok {
116 return "unknown"
117 }
118
119 parts := strings.Split(auth[0], " ")
120 if !(len(parts) == 2 && parts[0] == "Basic") {
121 return "unknown"
122 }
123
124 info, err := base64.StdEncoding.DecodeString(parts[1])
125 if err != nil {
126 log.Println("ERROR: Failed to parse Basic authentication.")
127 return "unknown"
128 }
129
130 loginParts := strings.Split(string(info), ":")
131 if len(loginParts) != 2 {
132 return "unknown"
133 }
134 return loginParts[0]
135}
136
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800137// Handles a RequestAllMaches request.
138type requestAllMatchesHandler struct {
139 db Database
140}
141
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800142// Change structure of match objects in the database(1 per team) to
143// the old match structure(1 per match) that the webserver uses.
144// We use the information in this struct to identify which match object
145// corresponds to which old match structure object.
146type MatchAssemblyKey struct {
147 MatchNumber int32
148 SetNumber int32
149 CompLevel string
150}
151
Emily Markovabf24c9e2023-02-08 20:31:11 -0800152func findIndexInList(list []string, comp_level string) (int, error) {
153 for index, value := range list {
154 if value == comp_level {
155 return index, nil
156 }
157 }
158 return -1, errors.New(fmt.Sprint("Failed to find comp level ", comp_level, " in list ", list))
159}
160
Emily Markovab8551572023-03-22 19:49:39 -0700161func (handler requestAllMatchesHandler) teamHasBeenDataScouted(key MatchAssemblyKey, teamNumber string) (bool, error) {
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800162 stats, err := handler.db.ReturnStats2023ForTeam(
Emily Markovab8551572023-03-22 19:49:39 -0700163 teamNumber, key.MatchNumber, key.SetNumber, key.CompLevel)
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800164 if err != nil {
165 return false, err
166 }
167 return (len(stats) > 0), nil
168}
169
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800170func (handler requestAllMatchesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
171 requestBytes, err := io.ReadAll(req.Body)
172 if err != nil {
173 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
174 return
175 }
176
Philipp Schraderb7e75932022-03-26 16:18:34 -0700177 _, success := parseRequest(w, requestBytes, "RequestAllMatches", request_all_matches.GetRootAsRequestAllMatches)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800178 if !success {
179 return
180 }
181
182 matches, err := handler.db.ReturnMatches()
183 if err != nil {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700184 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800185 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800186 }
187
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800188 assembledMatches := map[MatchAssemblyKey]request_all_matches_response.MatchT{}
Emily Markovabf24c9e2023-02-08 20:31:11 -0800189
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800190 for _, match := range matches {
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800191 key := MatchAssemblyKey{match.MatchNumber, match.SetNumber, match.CompLevel}
192
193 // Retrieve the converted match structure we have assembled so
194 // far. If we haven't started assembling one yet, then start a
195 // new one.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800196 entry, ok := assembledMatches[key]
197 if !ok {
198 entry = request_all_matches_response.MatchT{
199 MatchNumber: match.MatchNumber,
200 SetNumber: match.SetNumber,
201 CompLevel: match.CompLevel,
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800202 DataScouted: &request_all_matches_response.ScoutedLevelT{},
Emily Markovabf24c9e2023-02-08 20:31:11 -0800203 }
204 }
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800205
Emily Markovab8551572023-03-22 19:49:39 -0700206 var team *string
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800207 var dataScoutedTeam *bool
208
209 // Fill in the field for the match that we have in in the
210 // database. In the database, each match row only has 1 team
211 // number.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800212 switch match.Alliance {
213 case "R":
214 switch match.AlliancePosition {
215 case 1:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800216 team = &entry.R1
217 dataScoutedTeam = &entry.DataScouted.R1
Emily Markovabf24c9e2023-02-08 20:31:11 -0800218 case 2:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800219 team = &entry.R2
220 dataScoutedTeam = &entry.DataScouted.R2
Emily Markovabf24c9e2023-02-08 20:31:11 -0800221 case 3:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800222 team = &entry.R3
223 dataScoutedTeam = &entry.DataScouted.R3
Emily Markovabf24c9e2023-02-08 20:31:11 -0800224 default:
225 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown red position ", strconv.Itoa(int(match.AlliancePosition)), " in match ", strconv.Itoa(int(match.MatchNumber))))
226 return
227 }
228 case "B":
229 switch match.AlliancePosition {
230 case 1:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800231 team = &entry.B1
232 dataScoutedTeam = &entry.DataScouted.B1
Emily Markovabf24c9e2023-02-08 20:31:11 -0800233 case 2:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800234 team = &entry.B2
235 dataScoutedTeam = &entry.DataScouted.B2
Emily Markovabf24c9e2023-02-08 20:31:11 -0800236 case 3:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800237 team = &entry.B3
238 dataScoutedTeam = &entry.DataScouted.B3
Emily Markovabf24c9e2023-02-08 20:31:11 -0800239 default:
240 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown blue position ", strconv.Itoa(int(match.AlliancePosition)), " in match ", strconv.Itoa(int(match.MatchNumber))))
241 return
242 }
243 default:
244 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown alliance ", match.Alliance, " in match ", strconv.Itoa(int(match.AlliancePosition))))
245 return
246 }
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800247
248 *team = match.TeamNumber
249
250 // Figure out if this team has been data scouted already.
251 *dataScoutedTeam, err = handler.teamHasBeenDataScouted(key, match.TeamNumber)
252 if err != nil {
253 respondWithError(w, http.StatusInternalServerError, fmt.Sprint(
254 "Failed to determine data scouting status for team ",
255 strconv.Itoa(int(match.AlliancePosition)),
256 " in match ",
257 strconv.Itoa(int(match.MatchNumber)),
258 err))
259 return
260 }
261
Emily Markovabf24c9e2023-02-08 20:31:11 -0800262 assembledMatches[key] = entry
263 }
264
265 var response RequestAllMatchesResponseT
266 for _, match := range assembledMatches {
267 copied_match := match
268 response.MatchList = append(response.MatchList, &copied_match)
269 }
270
271 var MATCH_TYPE_ORDERING = []string{"qm", "ef", "qf", "sf", "f"}
272
273 err = nil
274 sort.Slice(response.MatchList, func(i, j int) bool {
275 if err != nil {
276 return false
277 }
278 a := response.MatchList[i]
279 b := response.MatchList[j]
280
Emily Markovaabcac6e2023-02-18 17:50:03 -0800281 aMatchTypeIndex, err2 := findIndexInList(MATCH_TYPE_ORDERING, a.CompLevel)
282 if err2 != nil {
283 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 -0800284 return false
285 }
Emily Markovaabcac6e2023-02-18 17:50:03 -0800286 bMatchTypeIndex, err2 := findIndexInList(MATCH_TYPE_ORDERING, b.CompLevel)
287 if err2 != nil {
288 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 -0800289 return false
290 }
291
292 if aMatchTypeIndex < bMatchTypeIndex {
293 return true
294 }
295 if aMatchTypeIndex > bMatchTypeIndex {
296 return false
297 }
298
299 // Then sort by match number. E.g. in semi finals, all match 1 rounds
300 // are done first. Then come match 2 rounds. And then, if necessary,
301 // the match 3 rounds.
302 aMatchNumber := a.MatchNumber
303 bMatchNumber := b.MatchNumber
304 if aMatchNumber < bMatchNumber {
305 return true
306 }
307 if aMatchNumber > bMatchNumber {
308 return false
309 }
310 // Lastly, sort by set number. I.e. Semi Final 1 Match 1 happens first.
311 // Then comes Semi Final 2 Match 1. Then comes Semi Final 1 Match 2. Then
312 // Semi Final 2 Match 2.
313 aSetNumber := a.SetNumber
314 bSetNumber := b.SetNumber
315 if aSetNumber < bSetNumber {
316 return true
317 }
318 if aSetNumber > bSetNumber {
319 return false
320 }
321 return true
322 })
323
324 if err != nil {
325 // check if error happened during sorting and notify webpage if that
326 respondWithError(w, http.StatusInternalServerError, fmt.Sprint(err))
327 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800328 }
329
330 builder := flatbuffers.NewBuilder(50 * 1024)
331 builder.Finish((&response).Pack(builder))
332 w.Write(builder.FinishedBytes())
333}
334
Alex Perry81f96ba2022-03-13 18:26:19 -0700335type submitNoteScoutingHandler struct {
336 db Database
337}
338
339func (handler submitNoteScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
340 requestBytes, err := io.ReadAll(req.Body)
341 if err != nil {
342 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
343 return
344 }
345
Philipp Schraderb7e75932022-03-26 16:18:34 -0700346 request, success := parseRequest(w, requestBytes, "SubmitNotes", submit_notes.GetRootAsSubmitNotes)
Alex Perry81f96ba2022-03-13 18:26:19 -0700347 if !success {
348 return
349 }
350
Filip Kujawaf947cb42022-11-21 10:00:30 -0800351 err = handler.db.AddNotes(db.NotesData{
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800352 TeamNumber: request.Team(),
353 Notes: string(request.Notes()),
354 GoodDriving: bool(request.GoodDriving()),
355 BadDriving: bool(request.BadDriving()),
Filip Kujawa6f7f0b32023-03-30 13:26:08 -0700356 SolidPickup: bool(request.SolidPickup()),
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800357 SketchyPlacing: bool(request.SketchyPlacing()),
358 GoodDefense: bool(request.GoodDefense()),
359 BadDefense: bool(request.BadDefense()),
360 EasilyDefended: bool(request.EasilyDefended()),
Filip Kujawaf947cb42022-11-21 10:00:30 -0800361 })
Alex Perry81f96ba2022-03-13 18:26:19 -0700362 if err != nil {
363 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert notes: %v", err))
364 return
365 }
366
367 var response SubmitNotesResponseT
368 builder := flatbuffers.NewBuilder(10)
369 builder.Finish((&response).Pack(builder))
370 w.Write(builder.FinishedBytes())
371}
372
Emily Markova1abe9782023-03-11 19:45:38 -0800373func ConvertActionsToStat(submitActions *submit_actions.SubmitActions) (db.Stats2023, error) {
374 overall_time := int64(0)
375 cycles := int64(0)
376 picked_up := false
377 lastPlacedTime := int64(0)
378 stat := db.Stats2023{TeamNumber: string(submitActions.TeamNumber()), MatchNumber: submitActions.MatchNumber(), SetNumber: submitActions.SetNumber(), CompLevel: string(submitActions.CompLevel()),
379 StartingQuadrant: 0, LowCubesAuto: 0, MiddleCubesAuto: 0, HighCubesAuto: 0, CubesDroppedAuto: 0,
380 LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 0, ConesDroppedAuto: 0, LowCubes: 0, MiddleCubes: 0, HighCubes: 0,
381 CubesDropped: 0, LowCones: 0, MiddleCones: 0, HighCones: 0, ConesDropped: 0, AvgCycle: 0, CollectedBy: string(submitActions.CollectedBy()),
382 }
383 // Loop over all actions.
384 for i := 0; i < submitActions.ActionsListLength(); i++ {
385 var action submit_actions.Action
386 if !submitActions.ActionsList(&action, i) {
387 return db.Stats2023{}, errors.New(fmt.Sprintf("Failed to parse submit_actions.Action"))
388 }
389 actionTable := new(flatbuffers.Table)
390 action_type := action.ActionTakenType()
391 if !action.ActionTaken(actionTable) {
392 return db.Stats2023{}, errors.New(fmt.Sprint("Failed to parse sub-action or sub-action was missing"))
393 }
394 if action_type == submit_actions.ActionTypeStartMatchAction {
395 var startMatchAction submit_actions.StartMatchAction
396 startMatchAction.Init(actionTable.Bytes, actionTable.Pos)
397 stat.StartingQuadrant = startMatchAction.Position()
Emily Markova46a69bf2023-03-22 20:45:52 -0700398 } else if action_type == submit_actions.ActionTypeAutoBalanceAction {
399 var autoBalanceAction submit_actions.AutoBalanceAction
400 autoBalanceAction.Init(actionTable.Bytes, actionTable.Pos)
401 if autoBalanceAction.Docked() {
402 stat.DockedAuto = true
403 }
404 if autoBalanceAction.Engaged() {
405 stat.EngagedAuto = true
406 }
Emily Markova1abe9782023-03-11 19:45:38 -0800407 } else if action_type == submit_actions.ActionTypePickupObjectAction {
408 var pick_up_action submit_actions.PickupObjectAction
409 pick_up_action.Init(actionTable.Bytes, actionTable.Pos)
410 if picked_up == true {
411 object := pick_up_action.ObjectType().String()
412 auto := pick_up_action.Auto()
413 if object == "kCube" && auto == false {
414 stat.CubesDropped += 1
415 } else if object == "kCube" && auto == true {
416 stat.CubesDroppedAuto += 1
417 } else if object == "kCone" && auto == false {
418 stat.ConesDropped += 1
419 } else if object == "kCube" && auto == true {
420 stat.ConesDroppedAuto += 1
421 }
422 } else {
423 picked_up = true
424 }
425 } else if action_type == submit_actions.ActionTypePlaceObjectAction {
426 var place_action submit_actions.PlaceObjectAction
427 place_action.Init(actionTable.Bytes, actionTable.Pos)
428 if !picked_up {
429 return db.Stats2023{}, errors.New(fmt.Sprintf("Got PlaceObjectAction without corresponding PickupObjectAction"))
430 }
431 object := place_action.ObjectType()
432 level := place_action.ScoreLevel()
433 auto := place_action.Auto()
434 if object == 0 && level == 0 && auto == true {
435 stat.LowCubesAuto += 1
436 } else if object == 0 && level == 0 && auto == false {
437 stat.LowCubes += 1
438 } else if object == 0 && level == 1 && auto == true {
439 stat.MiddleCubesAuto += 1
440 } else if object == 0 && level == 1 && auto == false {
441 stat.MiddleCubes += 1
442 } else if object == 0 && level == 2 && auto == true {
443 stat.HighCubesAuto += 1
444 } else if object == 0 && level == 2 && auto == false {
445 stat.HighCubes += 1
446 } else if object == 1 && level == 0 && auto == true {
447 stat.LowConesAuto += 1
448 } else if object == 1 && level == 0 && auto == false {
449 stat.LowCones += 1
450 } else if object == 1 && level == 1 && auto == true {
451 stat.MiddleConesAuto += 1
452 } else if object == 1 && level == 1 && auto == false {
453 stat.MiddleCones += 1
454 } else if object == 1 && level == 2 && auto == true {
455 stat.HighConesAuto += 1
456 } else if object == 1 && level == 2 && auto == false {
457 stat.HighCones += 1
458 } else {
459 return db.Stats2023{}, errors.New(fmt.Sprintf("Got unknown ObjectType/ScoreLevel/Auto combination"))
460 }
461 picked_up = false
462 if lastPlacedTime != int64(0) {
463 // If this is not the first time we place,
464 // start counting cycle time. We define cycle
465 // time as the time between placements.
466 overall_time += int64(action.Timestamp()) - lastPlacedTime
467 cycles += 1
468 }
469 lastPlacedTime = int64(action.Timestamp())
Emily Markova46a69bf2023-03-22 20:45:52 -0700470 } else if action_type == submit_actions.ActionTypeEndMatchAction {
471 var endMatchAction submit_actions.EndMatchAction
472 endMatchAction.Init(actionTable.Bytes, actionTable.Pos)
473 if endMatchAction.Docked() {
474 stat.Docked = true
475 }
476 if endMatchAction.Engaged() {
477 stat.Engaged = true
478 }
Emily Markova1abe9782023-03-11 19:45:38 -0800479 }
480 }
481 if cycles != 0 {
Philipp Schrader8c878a22023-03-20 22:36:38 -0700482 stat.AvgCycle = overall_time / cycles
Emily Markova1abe9782023-03-11 19:45:38 -0800483 } else {
484 stat.AvgCycle = 0
485 }
486 return stat, nil
487}
488
Emily Markova290147d2023-03-03 22:40:06 -0800489// Handles a Request2023DataScouting request.
490type request2023DataScoutingHandler struct {
491 db Database
492}
493
494func (handler request2023DataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
495 requestBytes, err := io.ReadAll(req.Body)
496 if err != nil {
497 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
498 return
499 }
500
501 _, success := parseRequest(w, requestBytes, "Request2023DataScouting", request_2023_data_scouting.GetRootAsRequest2023DataScouting)
502 if !success {
503 return
504 }
505
506 stats, err := handler.db.ReturnStats2023()
507 if err != nil {
508 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
509 return
510 }
511
512 var response Request2023DataScoutingResponseT
513 for _, stat := range stats {
514 response.StatsList = append(response.StatsList, &request_2023_data_scouting_response.Stats2023T{
515 TeamNumber: stat.TeamNumber,
516 MatchNumber: stat.MatchNumber,
517 SetNumber: stat.SetNumber,
518 CompLevel: stat.CompLevel,
519 StartingQuadrant: stat.StartingQuadrant,
520 LowCubesAuto: stat.LowCubesAuto,
521 MiddleCubesAuto: stat.MiddleCubesAuto,
522 HighCubesAuto: stat.HighCubesAuto,
523 CubesDroppedAuto: stat.CubesDroppedAuto,
524 LowConesAuto: stat.LowConesAuto,
525 MiddleConesAuto: stat.MiddleConesAuto,
526 HighConesAuto: stat.HighConesAuto,
527 ConesDroppedAuto: stat.ConesDroppedAuto,
528 LowCubes: stat.LowCubes,
529 MiddleCubes: stat.MiddleCubes,
530 HighCubes: stat.HighCubes,
531 CubesDropped: stat.CubesDropped,
532 LowCones: stat.LowCones,
533 MiddleCones: stat.MiddleCones,
534 HighCones: stat.HighCones,
535 ConesDropped: stat.ConesDropped,
536 AvgCycle: stat.AvgCycle,
Emily Markova46a69bf2023-03-22 20:45:52 -0700537 DockedAuto: stat.DockedAuto,
538 EngagedAuto: stat.EngagedAuto,
539 Docked: stat.Docked,
540 Engaged: stat.Engaged,
Emily Markova290147d2023-03-03 22:40:06 -0800541 CollectedBy: stat.CollectedBy,
542 })
543 }
544
545 builder := flatbuffers.NewBuilder(50 * 1024)
546 builder.Finish((&response).Pack(builder))
547 w.Write(builder.FinishedBytes())
548}
549
Alex Perry81f96ba2022-03-13 18:26:19 -0700550type requestNotesForTeamHandler struct {
551 db Database
552}
553
554func (handler requestNotesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
555 requestBytes, err := io.ReadAll(req.Body)
556 if err != nil {
557 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
558 return
559 }
560
Philipp Schraderb7e75932022-03-26 16:18:34 -0700561 request, success := parseRequest(w, requestBytes, "RequestNotesForTeam", request_notes_for_team.GetRootAsRequestNotesForTeam)
Alex Perry81f96ba2022-03-13 18:26:19 -0700562 if !success {
563 return
564 }
565
Philipp Schradereecb8962022-06-01 21:02:42 -0700566 notes, err := handler.db.QueryNotes(request.Team())
Alex Perry81f96ba2022-03-13 18:26:19 -0700567 if err != nil {
568 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query notes: %v", err))
569 return
570 }
571
572 var response RequestNotesForTeamResponseT
Philipp Schradereecb8962022-06-01 21:02:42 -0700573 for _, data := range notes {
Alex Perry81f96ba2022-03-13 18:26:19 -0700574 response.Notes = append(response.Notes, &request_notes_for_team_response.NoteT{data})
575 }
576
577 builder := flatbuffers.NewBuilder(1024)
578 builder.Finish((&response).Pack(builder))
579 w.Write(builder.FinishedBytes())
580}
581
Milo Lin1d59f0c2022-06-22 20:30:58 -0700582type requestShiftScheduleHandler struct {
583 db Database
584}
585
586func (handler requestShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
587 requestBytes, err := io.ReadAll(req.Body)
588 if err != nil {
589 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
590 return
591 }
592
593 _, success := parseRequest(w, requestBytes, "RequestShiftSchedule", request_shift_schedule.GetRootAsRequestShiftSchedule)
594 if !success {
595 return
596 }
597
598 shiftData, err := handler.db.ReturnAllShifts()
599 if err != nil {
600 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query shift schedule: %v", err))
601 return
602 }
603
604 var response RequestShiftScheduleResponseT
605 for _, shifts := range shiftData {
606 response.ShiftSchedule = append(response.ShiftSchedule, &request_shift_schedule_response.MatchAssignmentT{
607 MatchNumber: shifts.MatchNumber,
608 R1scouter: shifts.R1scouter,
609 R2scouter: shifts.R2scouter,
610 R3scouter: shifts.R3scouter,
611 B1scouter: shifts.B1scouter,
612 B2scouter: shifts.B2scouter,
613 B3scouter: shifts.B3scouter,
614 })
615 }
616
617 builder := flatbuffers.NewBuilder(1024)
618 builder.Finish((&response).Pack(builder))
619 w.Write(builder.FinishedBytes())
620}
621
622type submitShiftScheduleHandler struct {
623 db Database
624}
625
626func (handler submitShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
627 // Get the username of the person submitting the data.
628 username := parseUsername(req)
629
630 requestBytes, err := io.ReadAll(req.Body)
631 if err != nil {
632 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
633 return
634 }
635
636 request, success := parseRequest[SubmitShiftSchedule](w, requestBytes, "SubmitShiftSchedule", submit_shift_schedule.GetRootAsSubmitShiftSchedule)
637 if !success {
638 return
639 }
640
641 log.Println("Got shift schedule from", username)
642 shift_schedule_length := request.ShiftScheduleLength()
643 for i := 0; i < shift_schedule_length; i++ {
644 var match_assignment submit_shift_schedule.MatchAssignment
645 request.ShiftSchedule(&match_assignment, i)
646 current_shift := db.Shift{
647 MatchNumber: match_assignment.MatchNumber(),
648 R1scouter: string(match_assignment.R1scouter()),
649 R2scouter: string(match_assignment.R2scouter()),
650 R3scouter: string(match_assignment.R3scouter()),
651 B1scouter: string(match_assignment.B1scouter()),
652 B2scouter: string(match_assignment.B2scouter()),
653 B3scouter: string(match_assignment.B3scouter()),
654 }
655 err = handler.db.AddToShift(current_shift)
656 if err != nil {
657 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit shift schedule: ", err))
658 return
659 }
660 }
661
662 builder := flatbuffers.NewBuilder(50 * 1024)
663 builder.Finish((&SubmitShiftScheduleResponseT{}).Pack(builder))
664 w.Write(builder.FinishedBytes())
665}
666
Filip Kujawa210a03b2022-11-24 14:41:11 -0800667type SubmitDriverRankingHandler struct {
668 db Database
669}
670
671func (handler SubmitDriverRankingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
672 requestBytes, err := io.ReadAll(req.Body)
673 if err != nil {
674 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
675 return
676 }
677
678 request, success := parseRequest(w, requestBytes, "SubmitDriverRanking", submit_driver_ranking.GetRootAsSubmitDriverRanking)
679 if !success {
680 return
681 }
682
683 err = handler.db.AddDriverRanking(db.DriverRankingData{
684 MatchNumber: request.MatchNumber(),
685 Rank1: request.Rank1(),
686 Rank2: request.Rank2(),
687 Rank3: request.Rank3(),
688 })
689
690 if err != nil {
691 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert driver ranking: %v", err))
692 return
693 }
694
695 var response SubmitDriverRankingResponseT
696 builder := flatbuffers.NewBuilder(10)
697 builder.Finish((&response).Pack(builder))
698 w.Write(builder.FinishedBytes())
699}
700
Filip Kujawaf882e022022-12-14 13:14:08 -0800701type requestAllNotesHandler struct {
702 db Database
703}
704
705func (handler requestAllNotesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
706 requestBytes, err := io.ReadAll(req.Body)
707 if err != nil {
708 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
709 return
710 }
711
712 _, success := parseRequest(w, requestBytes, "RequestAllNotes", request_all_notes.GetRootAsRequestAllNotes)
713 if !success {
714 return
715 }
716
717 notes, err := handler.db.ReturnAllNotes()
718 if err != nil {
719 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
720 return
721 }
722
723 var response RequestAllNotesResponseT
724 for _, note := range notes {
725 response.NoteList = append(response.NoteList, &request_all_notes_response.NoteT{
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800726 Team: note.TeamNumber,
727 Notes: note.Notes,
728 GoodDriving: note.GoodDriving,
729 BadDriving: note.BadDriving,
Filip Kujawa6f7f0b32023-03-30 13:26:08 -0700730 SolidPickup: note.SolidPickup,
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800731 SketchyPlacing: note.SketchyPlacing,
732 GoodDefense: note.GoodDefense,
733 BadDefense: note.BadDefense,
734 EasilyDefended: note.EasilyDefended,
Filip Kujawaf882e022022-12-14 13:14:08 -0800735 })
736 }
737
738 builder := flatbuffers.NewBuilder(50 * 1024)
739 builder.Finish((&response).Pack(builder))
740 w.Write(builder.FinishedBytes())
741}
742
743type requestAllDriverRankingsHandler struct {
744 db Database
745}
746
747func (handler requestAllDriverRankingsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
748 requestBytes, err := io.ReadAll(req.Body)
749 if err != nil {
750 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
751 return
752 }
753
754 _, success := parseRequest(w, requestBytes, "RequestAllDriverRankings", request_all_driver_rankings.GetRootAsRequestAllDriverRankings)
755 if !success {
756 return
757 }
758
759 rankings, err := handler.db.ReturnAllDriverRankings()
760 if err != nil {
761 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
762 return
763 }
764
765 var response RequestAllDriverRankingsResponseT
766 for _, ranking := range rankings {
767 response.DriverRankingList = append(response.DriverRankingList, &request_all_driver_rankings_response.RankingT{
768 MatchNumber: ranking.MatchNumber,
769 Rank1: ranking.Rank1,
770 Rank2: ranking.Rank2,
771 Rank3: ranking.Rank3,
772 })
773 }
774
775 builder := flatbuffers.NewBuilder(50 * 1024)
776 builder.Finish((&response).Pack(builder))
777 w.Write(builder.FinishedBytes())
778}
779
Philipp Schrader43c730b2023-02-26 20:27:44 -0800780func HandleRequests(db Database, scoutingServer server.ScoutingServer) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800781 scoutingServer.HandleFunc("/requests", unknown)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800782 scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
Filip Kujawaf882e022022-12-14 13:14:08 -0800783 scoutingServer.Handle("/requests/request/all_notes", requestAllNotesHandler{db})
784 scoutingServer.Handle("/requests/request/all_driver_rankings", requestAllDriverRankingsHandler{db})
Emily Markova290147d2023-03-03 22:40:06 -0800785 scoutingServer.Handle("/requests/request/2023_data_scouting", request2023DataScoutingHandler{db})
Alex Perry81f96ba2022-03-13 18:26:19 -0700786 scoutingServer.Handle("/requests/submit/submit_notes", submitNoteScoutingHandler{db})
787 scoutingServer.Handle("/requests/request/notes_for_team", requestNotesForTeamHandler{db})
Milo Lin1d59f0c2022-06-22 20:30:58 -0700788 scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
789 scoutingServer.Handle("/requests/request/shift_schedule", requestShiftScheduleHandler{db})
Filip Kujawa210a03b2022-11-24 14:41:11 -0800790 scoutingServer.Handle("/requests/submit/submit_driver_ranking", SubmitDriverRankingHandler{db})
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800791}