blob: e5b46807b8b4bb64801c4006f02601d57c20ba2d [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
Sabina Leaver9b4eb312023-02-20 19:58:17 -080059type Action = submit_actions.Action
Sabina Leaver759090b2023-01-14 20:42:56 -080060type SubmitActionsResponseT = submit_actions_response.SubmitActionsResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080061
Philipp Schrader8747f1b2022-02-23 23:56:22 -080062// The interface we expect the database abstraction to conform to.
63// We use an interface here because it makes unit testing easier.
64type Database interface {
Emily Markovabf24c9e2023-02-08 20:31:11 -080065 AddToMatch(db.TeamMatch) error
Milo Lin1d59f0c2022-06-22 20:30:58 -070066 AddToShift(db.Shift) error
Emily Markova290147d2023-03-03 22:40:06 -080067 AddToStats2023(db.Stats2023) error
Emily Markovabf24c9e2023-02-08 20:31:11 -080068 ReturnMatches() ([]db.TeamMatch, error)
Filip Kujawaf882e022022-12-14 13:14:08 -080069 ReturnAllNotes() ([]db.NotesData, error)
70 ReturnAllDriverRankings() ([]db.DriverRankingData, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070071 ReturnAllShifts() ([]db.Shift, error)
Emily Markova290147d2023-03-03 22:40:06 -080072 ReturnStats2023() ([]db.Stats2023, error)
Philipp Schrader0f7b6362023-03-11 14:02:48 -080073 ReturnStats2023ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string) ([]db.Stats2023, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070074 QueryAllShifts(int) ([]db.Shift, error)
Philipp Schradereecb8962022-06-01 21:02:42 -070075 QueryNotes(int32) ([]string, error)
Filip Kujawaf947cb42022-11-21 10:00:30 -080076 AddNotes(db.NotesData) error
Filip Kujawa210a03b2022-11-24 14:41:11 -080077 AddDriverRanking(db.DriverRankingData) error
Sabina Leaver9b4eb312023-02-20 19:58:17 -080078 AddAction(db.Action) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080079}
80
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080081// Handles unknown requests. Just returns a 404.
82func unknown(w http.ResponseWriter, req *http.Request) {
83 w.WriteHeader(http.StatusNotFound)
84}
85
86func respondWithError(w http.ResponseWriter, statusCode int, errorMessage string) {
87 builder := flatbuffers.NewBuilder(1024)
88 builder.Finish((&error_response.ErrorResponseT{
89 ErrorMessage: errorMessage,
90 }).Pack(builder))
91 w.WriteHeader(statusCode)
92 w.Write(builder.FinishedBytes())
93}
94
95func respondNotImplemented(w http.ResponseWriter) {
96 respondWithError(w, http.StatusNotImplemented, "")
97}
98
Philipp Schraderb7e75932022-03-26 16:18:34 -070099func 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 -0800100 success := true
101 defer func() {
102 if r := recover(); r != nil {
Philipp Schraderb7e75932022-03-26 16:18:34 -0700103 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse %s: %v", requestName, r))
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800104 success = false
105 }
106 }()
Philipp Schraderb7e75932022-03-26 16:18:34 -0700107 result := parser(buf, 0)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800108 return result, success
109}
110
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700111// Parses the authorization information that the browser inserts into the
112// headers. The authorization follows this format:
113//
Philipp Schrader35bb1532023-03-05 13:49:12 -0800114// req.Headers["Authorization"] = []string{"Basic <base64 encoded username:password>"}
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700115func parseUsername(req *http.Request) string {
116 auth, ok := req.Header["Authorization"]
117 if !ok {
118 return "unknown"
119 }
120
121 parts := strings.Split(auth[0], " ")
122 if !(len(parts) == 2 && parts[0] == "Basic") {
123 return "unknown"
124 }
125
126 info, err := base64.StdEncoding.DecodeString(parts[1])
127 if err != nil {
128 log.Println("ERROR: Failed to parse Basic authentication.")
129 return "unknown"
130 }
131
132 loginParts := strings.Split(string(info), ":")
133 if len(loginParts) != 2 {
134 return "unknown"
135 }
136 return loginParts[0]
137}
138
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800139// Handles a RequestAllMaches request.
140type requestAllMatchesHandler struct {
141 db Database
142}
143
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800144// Change structure of match objects in the database(1 per team) to
145// the old match structure(1 per match) that the webserver uses.
146// We use the information in this struct to identify which match object
147// corresponds to which old match structure object.
148type MatchAssemblyKey struct {
149 MatchNumber int32
150 SetNumber int32
151 CompLevel string
152}
153
Emily Markovabf24c9e2023-02-08 20:31:11 -0800154func findIndexInList(list []string, comp_level string) (int, error) {
155 for index, value := range list {
156 if value == comp_level {
157 return index, nil
158 }
159 }
160 return -1, errors.New(fmt.Sprint("Failed to find comp level ", comp_level, " in list ", list))
161}
162
Emily Markovab8551572023-03-22 19:49:39 -0700163func (handler requestAllMatchesHandler) teamHasBeenDataScouted(key MatchAssemblyKey, teamNumber string) (bool, error) {
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800164 stats, err := handler.db.ReturnStats2023ForTeam(
Emily Markovab8551572023-03-22 19:49:39 -0700165 teamNumber, key.MatchNumber, key.SetNumber, key.CompLevel)
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800166 if err != nil {
167 return false, err
168 }
169 return (len(stats) > 0), nil
170}
171
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800172func (handler requestAllMatchesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
173 requestBytes, err := io.ReadAll(req.Body)
174 if err != nil {
175 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
176 return
177 }
178
Philipp Schraderb7e75932022-03-26 16:18:34 -0700179 _, success := parseRequest(w, requestBytes, "RequestAllMatches", request_all_matches.GetRootAsRequestAllMatches)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800180 if !success {
181 return
182 }
183
184 matches, err := handler.db.ReturnMatches()
185 if err != nil {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700186 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800187 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800188 }
189
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800190 assembledMatches := map[MatchAssemblyKey]request_all_matches_response.MatchT{}
Emily Markovabf24c9e2023-02-08 20:31:11 -0800191
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800192 for _, match := range matches {
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800193 key := MatchAssemblyKey{match.MatchNumber, match.SetNumber, match.CompLevel}
194
195 // Retrieve the converted match structure we have assembled so
196 // far. If we haven't started assembling one yet, then start a
197 // new one.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800198 entry, ok := assembledMatches[key]
199 if !ok {
200 entry = request_all_matches_response.MatchT{
201 MatchNumber: match.MatchNumber,
202 SetNumber: match.SetNumber,
203 CompLevel: match.CompLevel,
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800204 DataScouted: &request_all_matches_response.ScoutedLevelT{},
Emily Markovabf24c9e2023-02-08 20:31:11 -0800205 }
206 }
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800207
Emily Markovab8551572023-03-22 19:49:39 -0700208 var team *string
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800209 var dataScoutedTeam *bool
210
211 // Fill in the field for the match that we have in in the
212 // database. In the database, each match row only has 1 team
213 // number.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800214 switch match.Alliance {
215 case "R":
216 switch match.AlliancePosition {
217 case 1:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800218 team = &entry.R1
219 dataScoutedTeam = &entry.DataScouted.R1
Emily Markovabf24c9e2023-02-08 20:31:11 -0800220 case 2:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800221 team = &entry.R2
222 dataScoutedTeam = &entry.DataScouted.R2
Emily Markovabf24c9e2023-02-08 20:31:11 -0800223 case 3:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800224 team = &entry.R3
225 dataScoutedTeam = &entry.DataScouted.R3
Emily Markovabf24c9e2023-02-08 20:31:11 -0800226 default:
227 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown red position ", strconv.Itoa(int(match.AlliancePosition)), " in match ", strconv.Itoa(int(match.MatchNumber))))
228 return
229 }
230 case "B":
231 switch match.AlliancePosition {
232 case 1:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800233 team = &entry.B1
234 dataScoutedTeam = &entry.DataScouted.B1
Emily Markovabf24c9e2023-02-08 20:31:11 -0800235 case 2:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800236 team = &entry.B2
237 dataScoutedTeam = &entry.DataScouted.B2
Emily Markovabf24c9e2023-02-08 20:31:11 -0800238 case 3:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800239 team = &entry.B3
240 dataScoutedTeam = &entry.DataScouted.B3
Emily Markovabf24c9e2023-02-08 20:31:11 -0800241 default:
242 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown blue position ", strconv.Itoa(int(match.AlliancePosition)), " in match ", strconv.Itoa(int(match.MatchNumber))))
243 return
244 }
245 default:
246 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown alliance ", match.Alliance, " in match ", strconv.Itoa(int(match.AlliancePosition))))
247 return
248 }
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800249
250 *team = match.TeamNumber
251
252 // Figure out if this team has been data scouted already.
253 *dataScoutedTeam, err = handler.teamHasBeenDataScouted(key, match.TeamNumber)
254 if err != nil {
255 respondWithError(w, http.StatusInternalServerError, fmt.Sprint(
256 "Failed to determine data scouting status for team ",
257 strconv.Itoa(int(match.AlliancePosition)),
258 " in match ",
259 strconv.Itoa(int(match.MatchNumber)),
260 err))
261 return
262 }
263
Emily Markovabf24c9e2023-02-08 20:31:11 -0800264 assembledMatches[key] = entry
265 }
266
267 var response RequestAllMatchesResponseT
268 for _, match := range assembledMatches {
269 copied_match := match
270 response.MatchList = append(response.MatchList, &copied_match)
271 }
272
273 var MATCH_TYPE_ORDERING = []string{"qm", "ef", "qf", "sf", "f"}
274
275 err = nil
276 sort.Slice(response.MatchList, func(i, j int) bool {
277 if err != nil {
278 return false
279 }
280 a := response.MatchList[i]
281 b := response.MatchList[j]
282
Emily Markovaabcac6e2023-02-18 17:50:03 -0800283 aMatchTypeIndex, err2 := findIndexInList(MATCH_TYPE_ORDERING, a.CompLevel)
284 if err2 != nil {
285 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 -0800286 return false
287 }
Emily Markovaabcac6e2023-02-18 17:50:03 -0800288 bMatchTypeIndex, err2 := findIndexInList(MATCH_TYPE_ORDERING, b.CompLevel)
289 if err2 != nil {
290 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 -0800291 return false
292 }
293
294 if aMatchTypeIndex < bMatchTypeIndex {
295 return true
296 }
297 if aMatchTypeIndex > bMatchTypeIndex {
298 return false
299 }
300
301 // Then sort by match number. E.g. in semi finals, all match 1 rounds
302 // are done first. Then come match 2 rounds. And then, if necessary,
303 // the match 3 rounds.
304 aMatchNumber := a.MatchNumber
305 bMatchNumber := b.MatchNumber
306 if aMatchNumber < bMatchNumber {
307 return true
308 }
309 if aMatchNumber > bMatchNumber {
310 return false
311 }
312 // Lastly, sort by set number. I.e. Semi Final 1 Match 1 happens first.
313 // Then comes Semi Final 2 Match 1. Then comes Semi Final 1 Match 2. Then
314 // Semi Final 2 Match 2.
315 aSetNumber := a.SetNumber
316 bSetNumber := b.SetNumber
317 if aSetNumber < bSetNumber {
318 return true
319 }
320 if aSetNumber > bSetNumber {
321 return false
322 }
323 return true
324 })
325
326 if err != nil {
327 // check if error happened during sorting and notify webpage if that
328 respondWithError(w, http.StatusInternalServerError, fmt.Sprint(err))
329 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800330 }
331
332 builder := flatbuffers.NewBuilder(50 * 1024)
333 builder.Finish((&response).Pack(builder))
334 w.Write(builder.FinishedBytes())
335}
336
Alex Perry81f96ba2022-03-13 18:26:19 -0700337type submitNoteScoutingHandler struct {
338 db Database
339}
340
341func (handler submitNoteScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
342 requestBytes, err := io.ReadAll(req.Body)
343 if err != nil {
344 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
345 return
346 }
347
Philipp Schraderb7e75932022-03-26 16:18:34 -0700348 request, success := parseRequest(w, requestBytes, "SubmitNotes", submit_notes.GetRootAsSubmitNotes)
Alex Perry81f96ba2022-03-13 18:26:19 -0700349 if !success {
350 return
351 }
352
Filip Kujawaf947cb42022-11-21 10:00:30 -0800353 err = handler.db.AddNotes(db.NotesData{
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800354 TeamNumber: request.Team(),
355 Notes: string(request.Notes()),
356 GoodDriving: bool(request.GoodDriving()),
357 BadDriving: bool(request.BadDriving()),
Filip Kujawa11dc4c92023-04-13 08:55:43 -0700358 SolidPlacing: bool(request.SolidPlacing()),
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800359 SketchyPlacing: bool(request.SketchyPlacing()),
360 GoodDefense: bool(request.GoodDefense()),
361 BadDefense: bool(request.BadDefense()),
362 EasilyDefended: bool(request.EasilyDefended()),
Filip Kujawaf947cb42022-11-21 10:00:30 -0800363 })
Alex Perry81f96ba2022-03-13 18:26:19 -0700364 if err != nil {
365 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert notes: %v", err))
366 return
367 }
368
369 var response SubmitNotesResponseT
370 builder := flatbuffers.NewBuilder(10)
371 builder.Finish((&response).Pack(builder))
372 w.Write(builder.FinishedBytes())
373}
374
Emily Markova1abe9782023-03-11 19:45:38 -0800375func ConvertActionsToStat(submitActions *submit_actions.SubmitActions) (db.Stats2023, error) {
376 overall_time := int64(0)
377 cycles := int64(0)
378 picked_up := false
379 lastPlacedTime := int64(0)
Philipp Schrader4b489222023-04-15 16:40:16 -0700380 stat := db.Stats2023{
381 PreScouting: submitActions.PreScouting(),
382 TeamNumber: string(submitActions.TeamNumber()), MatchNumber: submitActions.MatchNumber(), SetNumber: submitActions.SetNumber(), CompLevel: string(submitActions.CompLevel()),
Emily Markova1abe9782023-03-11 19:45:38 -0800383 StartingQuadrant: 0, LowCubesAuto: 0, MiddleCubesAuto: 0, HighCubesAuto: 0, CubesDroppedAuto: 0,
384 LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 0, ConesDroppedAuto: 0, LowCubes: 0, MiddleCubes: 0, HighCubes: 0,
Philipp Schradere11114f2023-04-15 17:04:25 -0700385 CubesDropped: 0, LowCones: 0, MiddleCones: 0, HighCones: 0, ConesDropped: 0, SuperchargedPieces: 0, AvgCycle: 0, CollectedBy: "",
Emily Markova1abe9782023-03-11 19:45:38 -0800386 }
387 // Loop over all actions.
388 for i := 0; i < submitActions.ActionsListLength(); i++ {
389 var action submit_actions.Action
390 if !submitActions.ActionsList(&action, i) {
391 return db.Stats2023{}, errors.New(fmt.Sprintf("Failed to parse submit_actions.Action"))
392 }
393 actionTable := new(flatbuffers.Table)
394 action_type := action.ActionTakenType()
395 if !action.ActionTaken(actionTable) {
396 return db.Stats2023{}, errors.New(fmt.Sprint("Failed to parse sub-action or sub-action was missing"))
397 }
398 if action_type == submit_actions.ActionTypeStartMatchAction {
399 var startMatchAction submit_actions.StartMatchAction
400 startMatchAction.Init(actionTable.Bytes, actionTable.Pos)
401 stat.StartingQuadrant = startMatchAction.Position()
Filip Kujawa0b4b1e52023-04-15 14:05:40 -0700402 } else if action_type == submit_actions.ActionTypeMobilityAction {
403 var mobilityAction submit_actions.MobilityAction
404 mobilityAction.Init(actionTable.Bytes, actionTable.Pos)
405 if mobilityAction.Mobility() {
406 stat.Mobility = true
407 }
408
Emily Markova46a69bf2023-03-22 20:45:52 -0700409 } else if action_type == submit_actions.ActionTypeAutoBalanceAction {
410 var autoBalanceAction submit_actions.AutoBalanceAction
411 autoBalanceAction.Init(actionTable.Bytes, actionTable.Pos)
412 if autoBalanceAction.Docked() {
413 stat.DockedAuto = true
414 }
415 if autoBalanceAction.Engaged() {
416 stat.EngagedAuto = true
417 }
Emily Markova63c63f62023-03-29 20:57:35 -0700418 if autoBalanceAction.BalanceAttempt() {
419 stat.BalanceAttemptAuto = true
420 }
Emily Markova1abe9782023-03-11 19:45:38 -0800421 } else if action_type == submit_actions.ActionTypePickupObjectAction {
422 var pick_up_action submit_actions.PickupObjectAction
423 pick_up_action.Init(actionTable.Bytes, actionTable.Pos)
424 if picked_up == true {
425 object := pick_up_action.ObjectType().String()
426 auto := pick_up_action.Auto()
427 if object == "kCube" && auto == false {
428 stat.CubesDropped += 1
429 } else if object == "kCube" && auto == true {
430 stat.CubesDroppedAuto += 1
431 } else if object == "kCone" && auto == false {
432 stat.ConesDropped += 1
433 } else if object == "kCube" && auto == true {
434 stat.ConesDroppedAuto += 1
435 }
436 } else {
437 picked_up = true
438 }
439 } else if action_type == submit_actions.ActionTypePlaceObjectAction {
440 var place_action submit_actions.PlaceObjectAction
441 place_action.Init(actionTable.Bytes, actionTable.Pos)
442 if !picked_up {
443 return db.Stats2023{}, errors.New(fmt.Sprintf("Got PlaceObjectAction without corresponding PickupObjectAction"))
444 }
445 object := place_action.ObjectType()
446 level := place_action.ScoreLevel()
447 auto := place_action.Auto()
448 if object == 0 && level == 0 && auto == true {
449 stat.LowCubesAuto += 1
450 } else if object == 0 && level == 0 && auto == false {
451 stat.LowCubes += 1
452 } else if object == 0 && level == 1 && auto == true {
453 stat.MiddleCubesAuto += 1
454 } else if object == 0 && level == 1 && auto == false {
455 stat.MiddleCubes += 1
456 } else if object == 0 && level == 2 && auto == true {
457 stat.HighCubesAuto += 1
458 } else if object == 0 && level == 2 && auto == false {
459 stat.HighCubes += 1
460 } else if object == 1 && level == 0 && auto == true {
461 stat.LowConesAuto += 1
462 } else if object == 1 && level == 0 && auto == false {
463 stat.LowCones += 1
464 } else if object == 1 && level == 1 && auto == true {
465 stat.MiddleConesAuto += 1
466 } else if object == 1 && level == 1 && auto == false {
467 stat.MiddleCones += 1
468 } else if object == 1 && level == 2 && auto == true {
469 stat.HighConesAuto += 1
470 } else if object == 1 && level == 2 && auto == false {
471 stat.HighCones += 1
Filip Kujawa7a045e72023-04-13 08:41:09 -0700472 } else if level == 3 {
473 stat.SuperchargedPieces += 1
Emily Markova1abe9782023-03-11 19:45:38 -0800474 } else {
475 return db.Stats2023{}, errors.New(fmt.Sprintf("Got unknown ObjectType/ScoreLevel/Auto combination"))
476 }
477 picked_up = false
478 if lastPlacedTime != int64(0) {
479 // If this is not the first time we place,
480 // start counting cycle time. We define cycle
481 // time as the time between placements.
482 overall_time += int64(action.Timestamp()) - lastPlacedTime
483 cycles += 1
484 }
485 lastPlacedTime = int64(action.Timestamp())
Emily Markova46a69bf2023-03-22 20:45:52 -0700486 } else if action_type == submit_actions.ActionTypeEndMatchAction {
487 var endMatchAction submit_actions.EndMatchAction
488 endMatchAction.Init(actionTable.Bytes, actionTable.Pos)
489 if endMatchAction.Docked() {
490 stat.Docked = true
491 }
492 if endMatchAction.Engaged() {
493 stat.Engaged = true
494 }
Emily Markova63c63f62023-03-29 20:57:35 -0700495 if endMatchAction.BalanceAttempt() {
496 stat.BalanceAttempt = true
497 }
Emily Markova1abe9782023-03-11 19:45:38 -0800498 }
499 }
500 if cycles != 0 {
Philipp Schrader8c878a22023-03-20 22:36:38 -0700501 stat.AvgCycle = overall_time / cycles
Emily Markova1abe9782023-03-11 19:45:38 -0800502 } else {
503 stat.AvgCycle = 0
504 }
505 return stat, nil
506}
507
Emily Markova290147d2023-03-03 22:40:06 -0800508// Handles a Request2023DataScouting request.
509type request2023DataScoutingHandler struct {
510 db Database
511}
512
513func (handler request2023DataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
514 requestBytes, err := io.ReadAll(req.Body)
515 if err != nil {
516 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
517 return
518 }
519
520 _, success := parseRequest(w, requestBytes, "Request2023DataScouting", request_2023_data_scouting.GetRootAsRequest2023DataScouting)
521 if !success {
522 return
523 }
524
525 stats, err := handler.db.ReturnStats2023()
526 if err != nil {
527 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
528 return
529 }
530
531 var response Request2023DataScoutingResponseT
532 for _, stat := range stats {
533 response.StatsList = append(response.StatsList, &request_2023_data_scouting_response.Stats2023T{
Emily Markova63c63f62023-03-29 20:57:35 -0700534 TeamNumber: stat.TeamNumber,
535 MatchNumber: stat.MatchNumber,
536 SetNumber: stat.SetNumber,
537 CompLevel: stat.CompLevel,
538 StartingQuadrant: stat.StartingQuadrant,
539 LowCubesAuto: stat.LowCubesAuto,
540 MiddleCubesAuto: stat.MiddleCubesAuto,
541 HighCubesAuto: stat.HighCubesAuto,
542 CubesDroppedAuto: stat.CubesDroppedAuto,
543 LowConesAuto: stat.LowConesAuto,
544 MiddleConesAuto: stat.MiddleConesAuto,
545 HighConesAuto: stat.HighConesAuto,
546 ConesDroppedAuto: stat.ConesDroppedAuto,
547 LowCubes: stat.LowCubes,
548 MiddleCubes: stat.MiddleCubes,
549 HighCubes: stat.HighCubes,
550 CubesDropped: stat.CubesDropped,
551 LowCones: stat.LowCones,
552 MiddleCones: stat.MiddleCones,
553 HighCones: stat.HighCones,
554 ConesDropped: stat.ConesDropped,
Filip Kujawa7a045e72023-04-13 08:41:09 -0700555 SuperchargedPieces: stat.SuperchargedPieces,
Emily Markova63c63f62023-03-29 20:57:35 -0700556 AvgCycle: stat.AvgCycle,
Filip Kujawa0b4b1e52023-04-15 14:05:40 -0700557 Mobility: stat.Mobility,
Emily Markova63c63f62023-03-29 20:57:35 -0700558 DockedAuto: stat.DockedAuto,
559 EngagedAuto: stat.EngagedAuto,
560 BalanceAttemptAuto: stat.BalanceAttemptAuto,
561 Docked: stat.Docked,
562 Engaged: stat.Engaged,
563 BalanceAttempt: stat.BalanceAttempt,
564 CollectedBy: stat.CollectedBy,
Emily Markova290147d2023-03-03 22:40:06 -0800565 })
566 }
567
568 builder := flatbuffers.NewBuilder(50 * 1024)
569 builder.Finish((&response).Pack(builder))
570 w.Write(builder.FinishedBytes())
571}
572
Alex Perry81f96ba2022-03-13 18:26:19 -0700573type requestNotesForTeamHandler struct {
574 db Database
575}
576
577func (handler requestNotesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
578 requestBytes, err := io.ReadAll(req.Body)
579 if err != nil {
580 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
581 return
582 }
583
Philipp Schraderb7e75932022-03-26 16:18:34 -0700584 request, success := parseRequest(w, requestBytes, "RequestNotesForTeam", request_notes_for_team.GetRootAsRequestNotesForTeam)
Alex Perry81f96ba2022-03-13 18:26:19 -0700585 if !success {
586 return
587 }
588
Philipp Schradereecb8962022-06-01 21:02:42 -0700589 notes, err := handler.db.QueryNotes(request.Team())
Alex Perry81f96ba2022-03-13 18:26:19 -0700590 if err != nil {
591 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query notes: %v", err))
592 return
593 }
594
595 var response RequestNotesForTeamResponseT
Philipp Schradereecb8962022-06-01 21:02:42 -0700596 for _, data := range notes {
Alex Perry81f96ba2022-03-13 18:26:19 -0700597 response.Notes = append(response.Notes, &request_notes_for_team_response.NoteT{data})
598 }
599
600 builder := flatbuffers.NewBuilder(1024)
601 builder.Finish((&response).Pack(builder))
602 w.Write(builder.FinishedBytes())
603}
604
Milo Lin1d59f0c2022-06-22 20:30:58 -0700605type requestShiftScheduleHandler struct {
606 db Database
607}
608
609func (handler requestShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
610 requestBytes, err := io.ReadAll(req.Body)
611 if err != nil {
612 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
613 return
614 }
615
616 _, success := parseRequest(w, requestBytes, "RequestShiftSchedule", request_shift_schedule.GetRootAsRequestShiftSchedule)
617 if !success {
618 return
619 }
620
621 shiftData, err := handler.db.ReturnAllShifts()
622 if err != nil {
623 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query shift schedule: %v", err))
624 return
625 }
626
627 var response RequestShiftScheduleResponseT
628 for _, shifts := range shiftData {
629 response.ShiftSchedule = append(response.ShiftSchedule, &request_shift_schedule_response.MatchAssignmentT{
630 MatchNumber: shifts.MatchNumber,
631 R1scouter: shifts.R1scouter,
632 R2scouter: shifts.R2scouter,
633 R3scouter: shifts.R3scouter,
634 B1scouter: shifts.B1scouter,
635 B2scouter: shifts.B2scouter,
636 B3scouter: shifts.B3scouter,
637 })
638 }
639
640 builder := flatbuffers.NewBuilder(1024)
641 builder.Finish((&response).Pack(builder))
642 w.Write(builder.FinishedBytes())
643}
644
645type submitShiftScheduleHandler struct {
646 db Database
647}
648
649func (handler submitShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
650 // Get the username of the person submitting the data.
651 username := parseUsername(req)
652
653 requestBytes, err := io.ReadAll(req.Body)
654 if err != nil {
655 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
656 return
657 }
658
659 request, success := parseRequest[SubmitShiftSchedule](w, requestBytes, "SubmitShiftSchedule", submit_shift_schedule.GetRootAsSubmitShiftSchedule)
660 if !success {
661 return
662 }
663
664 log.Println("Got shift schedule from", username)
665 shift_schedule_length := request.ShiftScheduleLength()
666 for i := 0; i < shift_schedule_length; i++ {
667 var match_assignment submit_shift_schedule.MatchAssignment
668 request.ShiftSchedule(&match_assignment, i)
669 current_shift := db.Shift{
670 MatchNumber: match_assignment.MatchNumber(),
671 R1scouter: string(match_assignment.R1scouter()),
672 R2scouter: string(match_assignment.R2scouter()),
673 R3scouter: string(match_assignment.R3scouter()),
674 B1scouter: string(match_assignment.B1scouter()),
675 B2scouter: string(match_assignment.B2scouter()),
676 B3scouter: string(match_assignment.B3scouter()),
677 }
678 err = handler.db.AddToShift(current_shift)
679 if err != nil {
680 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit shift schedule: ", err))
681 return
682 }
683 }
684
685 builder := flatbuffers.NewBuilder(50 * 1024)
686 builder.Finish((&SubmitShiftScheduleResponseT{}).Pack(builder))
687 w.Write(builder.FinishedBytes())
688}
689
Filip Kujawa210a03b2022-11-24 14:41:11 -0800690type SubmitDriverRankingHandler struct {
691 db Database
692}
693
694func (handler SubmitDriverRankingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
695 requestBytes, err := io.ReadAll(req.Body)
696 if err != nil {
697 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
698 return
699 }
700
701 request, success := parseRequest(w, requestBytes, "SubmitDriverRanking", submit_driver_ranking.GetRootAsSubmitDriverRanking)
702 if !success {
703 return
704 }
705
706 err = handler.db.AddDriverRanking(db.DriverRankingData{
707 MatchNumber: request.MatchNumber(),
708 Rank1: request.Rank1(),
709 Rank2: request.Rank2(),
710 Rank3: request.Rank3(),
711 })
712
713 if err != nil {
714 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert driver ranking: %v", err))
715 return
716 }
717
718 var response SubmitDriverRankingResponseT
719 builder := flatbuffers.NewBuilder(10)
720 builder.Finish((&response).Pack(builder))
721 w.Write(builder.FinishedBytes())
722}
723
Filip Kujawaf882e022022-12-14 13:14:08 -0800724type requestAllNotesHandler struct {
725 db Database
726}
727
728func (handler requestAllNotesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
729 requestBytes, err := io.ReadAll(req.Body)
730 if err != nil {
731 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
732 return
733 }
734
735 _, success := parseRequest(w, requestBytes, "RequestAllNotes", request_all_notes.GetRootAsRequestAllNotes)
736 if !success {
737 return
738 }
739
740 notes, err := handler.db.ReturnAllNotes()
741 if err != nil {
742 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
743 return
744 }
745
746 var response RequestAllNotesResponseT
747 for _, note := range notes {
748 response.NoteList = append(response.NoteList, &request_all_notes_response.NoteT{
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800749 Team: note.TeamNumber,
750 Notes: note.Notes,
751 GoodDriving: note.GoodDriving,
752 BadDriving: note.BadDriving,
Filip Kujawa11dc4c92023-04-13 08:55:43 -0700753 SolidPlacing: note.SolidPlacing,
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800754 SketchyPlacing: note.SketchyPlacing,
755 GoodDefense: note.GoodDefense,
756 BadDefense: note.BadDefense,
757 EasilyDefended: note.EasilyDefended,
Filip Kujawaf882e022022-12-14 13:14:08 -0800758 })
759 }
760
761 builder := flatbuffers.NewBuilder(50 * 1024)
762 builder.Finish((&response).Pack(builder))
763 w.Write(builder.FinishedBytes())
764}
765
766type requestAllDriverRankingsHandler struct {
767 db Database
768}
769
770func (handler requestAllDriverRankingsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
771 requestBytes, err := io.ReadAll(req.Body)
772 if err != nil {
773 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
774 return
775 }
776
777 _, success := parseRequest(w, requestBytes, "RequestAllDriverRankings", request_all_driver_rankings.GetRootAsRequestAllDriverRankings)
778 if !success {
779 return
780 }
781
782 rankings, err := handler.db.ReturnAllDriverRankings()
783 if err != nil {
784 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
785 return
786 }
787
788 var response RequestAllDriverRankingsResponseT
789 for _, ranking := range rankings {
790 response.DriverRankingList = append(response.DriverRankingList, &request_all_driver_rankings_response.RankingT{
791 MatchNumber: ranking.MatchNumber,
792 Rank1: ranking.Rank1,
793 Rank2: ranking.Rank2,
794 Rank3: ranking.Rank3,
795 })
796 }
797
798 builder := flatbuffers.NewBuilder(50 * 1024)
799 builder.Finish((&response).Pack(builder))
800 w.Write(builder.FinishedBytes())
801}
802
Sabina Leaver9b4eb312023-02-20 19:58:17 -0800803type submitActionsHandler struct {
804 db Database
805}
806
807func (handler submitActionsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
808 // Get the username of the person submitting the data.
809 username := parseUsername(req)
810
811 requestBytes, err := io.ReadAll(req.Body)
812 if err != nil {
813 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
814 return
815 }
816
817 request, success := parseRequest(w, requestBytes, "SubmitActions", submit_actions.GetRootAsSubmitActions)
818 if !success {
819 return
820 }
821
822 log.Println("Got actions for match", request.MatchNumber(), "team", request.TeamNumber(), "from", username)
823
824 for i := 0; i < request.ActionsListLength(); i++ {
825
826 var action Action
827 request.ActionsList(&action, i)
828
829 dbAction := db.Action{
Philipp Schrader4b489222023-04-15 16:40:16 -0700830 PreScouting: request.PreScouting(),
Sabina Leaver9b4eb312023-02-20 19:58:17 -0800831 TeamNumber: string(request.TeamNumber()),
832 MatchNumber: request.MatchNumber(),
833 SetNumber: request.SetNumber(),
834 CompLevel: string(request.CompLevel()),
835 //TODO: Serialize CompletedAction
836 CompletedAction: []byte{},
Philipp Schraderba2c5a62023-04-16 22:12:21 -0700837 TimeStamp: action.Timestamp(),
Sabina Leaver9b4eb312023-02-20 19:58:17 -0800838 CollectedBy: username,
839 }
840
841 // Do some error checking.
842 if action.Timestamp() < 0 {
843 respondWithError(w, http.StatusBadRequest, fmt.Sprint(
844 "Invalid timestamp field value of ", action.Timestamp()))
845 return
846 }
847
848 err = handler.db.AddAction(dbAction)
849 if err != nil {
850 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to add action to database: ", err))
851 return
852 }
853 }
854
Philipp Schradere11114f2023-04-15 17:04:25 -0700855 stats, err := ConvertActionsToStat(request)
856 if err != nil {
857 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to convert actions to stats: ", err))
858 return
859 }
860
861 stats.CollectedBy = username
862
863 err = handler.db.AddToStats2023(stats)
864 if err != nil {
865 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit stats: ", stats, ": ", err))
866 return
867 }
868
Sabina Leaver9b4eb312023-02-20 19:58:17 -0800869 builder := flatbuffers.NewBuilder(50 * 1024)
870 builder.Finish((&SubmitActionsResponseT{}).Pack(builder))
871 w.Write(builder.FinishedBytes())
872}
873
Philipp Schrader43c730b2023-02-26 20:27:44 -0800874func HandleRequests(db Database, scoutingServer server.ScoutingServer) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800875 scoutingServer.HandleFunc("/requests", unknown)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800876 scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
Filip Kujawaf882e022022-12-14 13:14:08 -0800877 scoutingServer.Handle("/requests/request/all_notes", requestAllNotesHandler{db})
878 scoutingServer.Handle("/requests/request/all_driver_rankings", requestAllDriverRankingsHandler{db})
Emily Markova290147d2023-03-03 22:40:06 -0800879 scoutingServer.Handle("/requests/request/2023_data_scouting", request2023DataScoutingHandler{db})
Alex Perry81f96ba2022-03-13 18:26:19 -0700880 scoutingServer.Handle("/requests/submit/submit_notes", submitNoteScoutingHandler{db})
881 scoutingServer.Handle("/requests/request/notes_for_team", requestNotesForTeamHandler{db})
Milo Lin1d59f0c2022-06-22 20:30:58 -0700882 scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
883 scoutingServer.Handle("/requests/request/shift_schedule", requestShiftScheduleHandler{db})
Filip Kujawa210a03b2022-11-24 14:41:11 -0800884 scoutingServer.Handle("/requests/submit/submit_driver_ranking", SubmitDriverRankingHandler{db})
Sabina Leaver9b4eb312023-02-20 19:58:17 -0800885 scoutingServer.Handle("/requests/submit/submit_actions", submitActionsHandler{db})
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800886}