blob: 75b438bdf63c3b0c9ecfb28dd992236a62abfd60 [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"
Emily Markova8cb91312024-02-02 12:30:37 -080017 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/delete_2024_data_scouting"
18 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/delete_2024_data_scouting_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080019 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
Emily Markova290147d2023-03-03 22:40:06 -080020 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_2023_data_scouting"
21 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_2023_data_scouting_response"
Emily Markova8cb91312024-02-02 12:30:37 -080022 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_2024_data_scouting"
23 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_2024_data_scouting_response"
Filip Kujawaf882e022022-12-14 13:14:08 -080024 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_driver_rankings"
25 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_driver_rankings_response"
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080026 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches"
27 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches_response"
Filip Kujawaf882e022022-12-14 13:14:08 -080028 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_notes"
29 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_notes_response"
Emily Markova8e39f452023-12-23 12:17:30 -080030 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_pit_images"
31 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_pit_images_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070032 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team"
33 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team_response"
Emily Markovafaecfe12023-07-01 12:40:03 -070034 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_pit_images"
35 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_pit_images_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070036 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule"
37 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule_response"
Emily Markova8cb91312024-02-02 12:30:37 -080038 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_2024_actions"
39 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_2024_actions_response"
Sabina Leaver759090b2023-01-14 20:42:56 -080040 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_actions"
41 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_actions_response"
Filip Kujawa210a03b2022-11-24 14:41:11 -080042 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking"
43 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070044 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes"
45 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
Emily Markovafaecfe12023-07-01 12:40:03 -070046 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_pit_image"
47 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_pit_image_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070048 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule"
49 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080050 "github.com/frc971/971-Robot-Code/scouting/webserver/server"
51 flatbuffers "github.com/google/flatbuffers/go"
52)
53
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080054type RequestAllMatches = request_all_matches.RequestAllMatches
55type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
Filip Kujawaf882e022022-12-14 13:14:08 -080056type RequestAllDriverRankings = request_all_driver_rankings.RequestAllDriverRankings
57type RequestAllDriverRankingsResponseT = request_all_driver_rankings_response.RequestAllDriverRankingsResponseT
58type RequestAllNotes = request_all_notes.RequestAllNotes
59type RequestAllNotesResponseT = request_all_notes_response.RequestAllNotesResponseT
Emily Markova290147d2023-03-03 22:40:06 -080060type Request2023DataScouting = request_2023_data_scouting.Request2023DataScouting
61type Request2023DataScoutingResponseT = request_2023_data_scouting_response.Request2023DataScoutingResponseT
Emily Markova8cb91312024-02-02 12:30:37 -080062type Request2024DataScouting = request_2024_data_scouting.Request2024DataScouting
63type Request2024DataScoutingResponseT = request_2024_data_scouting_response.Request2024DataScoutingResponseT
Alex Perry81f96ba2022-03-13 18:26:19 -070064type SubmitNotes = submit_notes.SubmitNotes
65type SubmitNotesResponseT = submit_notes_response.SubmitNotesResponseT
Emily Markovafaecfe12023-07-01 12:40:03 -070066type SubmitPitImage = submit_pit_image.SubmitPitImage
67type SubmitPitImageResponseT = submit_pit_image_response.SubmitPitImageResponseT
68type RequestPitImages = request_pit_images.RequestPitImages
69type RequestPitImagesResponseT = request_pit_images_response.RequestPitImagesResponseT
Emily Markova8e39f452023-12-23 12:17:30 -080070type RequestAllPitImages = request_all_pit_images.RequestAllPitImages
71type RequestAllPitImagesResponseT = request_all_pit_images_response.RequestAllPitImagesResponseT
Alex Perry81f96ba2022-03-13 18:26:19 -070072type RequestNotesForTeam = request_notes_for_team.RequestNotesForTeam
73type RequestNotesForTeamResponseT = request_notes_for_team_response.RequestNotesForTeamResponseT
Milo Lin1d59f0c2022-06-22 20:30:58 -070074type RequestShiftSchedule = request_shift_schedule.RequestShiftSchedule
75type RequestShiftScheduleResponseT = request_shift_schedule_response.RequestShiftScheduleResponseT
76type SubmitShiftSchedule = submit_shift_schedule.SubmitShiftSchedule
77type SubmitShiftScheduleResponseT = submit_shift_schedule_response.SubmitShiftScheduleResponseT
Filip Kujawa210a03b2022-11-24 14:41:11 -080078type SubmitDriverRanking = submit_driver_ranking.SubmitDriverRanking
79type SubmitDriverRankingResponseT = submit_driver_ranking_response.SubmitDriverRankingResponseT
Sabina Leaver759090b2023-01-14 20:42:56 -080080type SubmitActions = submit_actions.SubmitActions
Sabina Leaver9b4eb312023-02-20 19:58:17 -080081type Action = submit_actions.Action
Emily Markova8cb91312024-02-02 12:30:37 -080082type Action2024 = submit_2024_actions.Action
Sabina Leaver759090b2023-01-14 20:42:56 -080083type SubmitActionsResponseT = submit_actions_response.SubmitActionsResponseT
Emily Markova8cb91312024-02-02 12:30:37 -080084type Submit2024Actions = submit_2024_actions.Submit2024Actions
85type Submit2024ActionsResponseT = submit_2024_actions_response.Submit2024ActionsResponseT
Filip Kujawac1ded372023-05-27 14:33:43 -070086type Delete2023DataScouting = delete_2023_data_scouting.Delete2023DataScouting
87type Delete2023DataScoutingResponseT = delete_2023_data_scouting_response.Delete2023DataScoutingResponseT
Emily Markova8cb91312024-02-02 12:30:37 -080088type Delete2024DataScouting = delete_2024_data_scouting.Delete2024DataScouting
89type Delete2024DataScoutingResponseT = delete_2024_data_scouting_response.Delete2024DataScoutingResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080090
Philipp Schrader8747f1b2022-02-23 23:56:22 -080091// The interface we expect the database abstraction to conform to.
92// We use an interface here because it makes unit testing easier.
93type Database interface {
Emily Markovabf24c9e2023-02-08 20:31:11 -080094 AddToMatch(db.TeamMatch) error
Milo Lin1d59f0c2022-06-22 20:30:58 -070095 AddToShift(db.Shift) error
Emily Markova290147d2023-03-03 22:40:06 -080096 AddToStats2023(db.Stats2023) error
Emily Markova8cb91312024-02-02 12:30:37 -080097 AddToStats2024(db.Stats2024) error
Emily Markovabf24c9e2023-02-08 20:31:11 -080098 ReturnMatches() ([]db.TeamMatch, error)
Filip Kujawaf882e022022-12-14 13:14:08 -080099 ReturnAllNotes() ([]db.NotesData, error)
100 ReturnAllDriverRankings() ([]db.DriverRankingData, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -0700101 ReturnAllShifts() ([]db.Shift, error)
Emily Markova290147d2023-03-03 22:40:06 -0800102 ReturnStats2023() ([]db.Stats2023, error)
Filip Kujawaf3f9def2023-04-20 13:46:46 -0700103 ReturnStats2023ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string, preScouting bool) ([]db.Stats2023, error)
Emily Markova8cb91312024-02-02 12:30:37 -0800104 ReturnStats2024() ([]db.Stats2024, error)
105 ReturnStats2024ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string, preScouting bool) ([]db.Stats2024, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -0700106 QueryAllShifts(int) ([]db.Shift, error)
Emily Markovae68b7632023-12-30 14:17:55 -0800107 QueryNotes(string) ([]string, error)
Emily Markovafaecfe12023-07-01 12:40:03 -0700108 QueryPitImages(string) ([]db.RequestedPitImage, error)
Emily Markova8e39f452023-12-23 12:17:30 -0800109 ReturnPitImages() ([]db.PitImage, error)
Filip Kujawaf947cb42022-11-21 10:00:30 -0800110 AddNotes(db.NotesData) error
Emily Markovafaecfe12023-07-01 12:40:03 -0700111 AddPitImage(db.PitImage) error
Filip Kujawa210a03b2022-11-24 14:41:11 -0800112 AddDriverRanking(db.DriverRankingData) error
Sabina Leaver9b4eb312023-02-20 19:58:17 -0800113 AddAction(db.Action) error
Filip Kujawac1ded372023-05-27 14:33:43 -0700114 DeleteFromStats(string, int32, int32, string) error
Emily Markova8cb91312024-02-02 12:30:37 -0800115 DeleteFromStats2024(string, int32, int32, string) error
Filip Kujawac1ded372023-05-27 14:33:43 -0700116 DeleteFromActions(string, int32, int32, string) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800117}
118
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800119// Handles unknown requests. Just returns a 404.
120func unknown(w http.ResponseWriter, req *http.Request) {
121 w.WriteHeader(http.StatusNotFound)
122}
123
124func respondWithError(w http.ResponseWriter, statusCode int, errorMessage string) {
125 builder := flatbuffers.NewBuilder(1024)
126 builder.Finish((&error_response.ErrorResponseT{
127 ErrorMessage: errorMessage,
128 }).Pack(builder))
129 w.WriteHeader(statusCode)
130 w.Write(builder.FinishedBytes())
131}
132
133func respondNotImplemented(w http.ResponseWriter) {
134 respondWithError(w, http.StatusNotImplemented, "")
135}
136
Philipp Schraderb7e75932022-03-26 16:18:34 -0700137func 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 -0800138 success := true
139 defer func() {
140 if r := recover(); r != nil {
Philipp Schraderb7e75932022-03-26 16:18:34 -0700141 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse %s: %v", requestName, r))
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800142 success = false
143 }
144 }()
Philipp Schraderb7e75932022-03-26 16:18:34 -0700145 result := parser(buf, 0)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800146 return result, success
147}
148
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700149// Parses the authorization information that the browser inserts into the
150// headers. The authorization follows this format:
151//
Philipp Schrader35bb1532023-03-05 13:49:12 -0800152// req.Headers["Authorization"] = []string{"Basic <base64 encoded username:password>"}
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700153func parseUsername(req *http.Request) string {
154 auth, ok := req.Header["Authorization"]
155 if !ok {
156 return "unknown"
157 }
158
159 parts := strings.Split(auth[0], " ")
160 if !(len(parts) == 2 && parts[0] == "Basic") {
161 return "unknown"
162 }
163
164 info, err := base64.StdEncoding.DecodeString(parts[1])
165 if err != nil {
166 log.Println("ERROR: Failed to parse Basic authentication.")
167 return "unknown"
168 }
169
170 loginParts := strings.Split(string(info), ":")
171 if len(loginParts) != 2 {
172 return "unknown"
173 }
174 return loginParts[0]
175}
176
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800177// Handles a RequestAllMaches request.
178type requestAllMatchesHandler struct {
179 db Database
180}
181
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800182// Change structure of match objects in the database(1 per team) to
183// the old match structure(1 per match) that the webserver uses.
184// We use the information in this struct to identify which match object
185// corresponds to which old match structure object.
186type MatchAssemblyKey struct {
187 MatchNumber int32
188 SetNumber int32
189 CompLevel string
190}
191
Emily Markovabf24c9e2023-02-08 20:31:11 -0800192func findIndexInList(list []string, comp_level string) (int, error) {
193 for index, value := range list {
194 if value == comp_level {
195 return index, nil
196 }
197 }
198 return -1, errors.New(fmt.Sprint("Failed to find comp level ", comp_level, " in list ", list))
199}
200
Emily Markovab8551572023-03-22 19:49:39 -0700201func (handler requestAllMatchesHandler) teamHasBeenDataScouted(key MatchAssemblyKey, teamNumber string) (bool, error) {
Emily Markova8cb91312024-02-02 12:30:37 -0800202 // TODO change this to reference 2024 stats
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800203 stats, err := handler.db.ReturnStats2023ForTeam(
Filip Kujawaf3f9def2023-04-20 13:46:46 -0700204 teamNumber, key.MatchNumber, key.SetNumber, key.CompLevel, false)
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800205 if err != nil {
206 return false, err
207 }
208 return (len(stats) > 0), nil
209}
210
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800211func (handler requestAllMatchesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
212 requestBytes, err := io.ReadAll(req.Body)
213 if err != nil {
214 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
215 return
216 }
217
Philipp Schraderb7e75932022-03-26 16:18:34 -0700218 _, success := parseRequest(w, requestBytes, "RequestAllMatches", request_all_matches.GetRootAsRequestAllMatches)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800219 if !success {
220 return
221 }
222
223 matches, err := handler.db.ReturnMatches()
224 if err != nil {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700225 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800226 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800227 }
228
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800229 assembledMatches := map[MatchAssemblyKey]request_all_matches_response.MatchT{}
Emily Markovabf24c9e2023-02-08 20:31:11 -0800230
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800231 for _, match := range matches {
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800232 key := MatchAssemblyKey{match.MatchNumber, match.SetNumber, match.CompLevel}
233
234 // Retrieve the converted match structure we have assembled so
235 // far. If we haven't started assembling one yet, then start a
236 // new one.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800237 entry, ok := assembledMatches[key]
238 if !ok {
239 entry = request_all_matches_response.MatchT{
240 MatchNumber: match.MatchNumber,
241 SetNumber: match.SetNumber,
242 CompLevel: match.CompLevel,
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800243 DataScouted: &request_all_matches_response.ScoutedLevelT{},
Emily Markovabf24c9e2023-02-08 20:31:11 -0800244 }
245 }
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800246
Emily Markovab8551572023-03-22 19:49:39 -0700247 var team *string
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800248 var dataScoutedTeam *bool
249
250 // Fill in the field for the match that we have in in the
251 // database. In the database, each match row only has 1 team
252 // number.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800253 switch match.Alliance {
254 case "R":
255 switch match.AlliancePosition {
256 case 1:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800257 team = &entry.R1
258 dataScoutedTeam = &entry.DataScouted.R1
Emily Markovabf24c9e2023-02-08 20:31:11 -0800259 case 2:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800260 team = &entry.R2
261 dataScoutedTeam = &entry.DataScouted.R2
Emily Markovabf24c9e2023-02-08 20:31:11 -0800262 case 3:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800263 team = &entry.R3
264 dataScoutedTeam = &entry.DataScouted.R3
Emily Markovabf24c9e2023-02-08 20:31:11 -0800265 default:
266 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown red position ", strconv.Itoa(int(match.AlliancePosition)), " in match ", strconv.Itoa(int(match.MatchNumber))))
267 return
268 }
269 case "B":
270 switch match.AlliancePosition {
271 case 1:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800272 team = &entry.B1
273 dataScoutedTeam = &entry.DataScouted.B1
Emily Markovabf24c9e2023-02-08 20:31:11 -0800274 case 2:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800275 team = &entry.B2
276 dataScoutedTeam = &entry.DataScouted.B2
Emily Markovabf24c9e2023-02-08 20:31:11 -0800277 case 3:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800278 team = &entry.B3
279 dataScoutedTeam = &entry.DataScouted.B3
Emily Markovabf24c9e2023-02-08 20:31:11 -0800280 default:
281 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown blue position ", strconv.Itoa(int(match.AlliancePosition)), " in match ", strconv.Itoa(int(match.MatchNumber))))
282 return
283 }
284 default:
285 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown alliance ", match.Alliance, " in match ", strconv.Itoa(int(match.AlliancePosition))))
286 return
287 }
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800288
289 *team = match.TeamNumber
290
291 // Figure out if this team has been data scouted already.
292 *dataScoutedTeam, err = handler.teamHasBeenDataScouted(key, match.TeamNumber)
293 if err != nil {
294 respondWithError(w, http.StatusInternalServerError, fmt.Sprint(
295 "Failed to determine data scouting status for team ",
296 strconv.Itoa(int(match.AlliancePosition)),
297 " in match ",
298 strconv.Itoa(int(match.MatchNumber)),
299 err))
300 return
301 }
302
Emily Markovabf24c9e2023-02-08 20:31:11 -0800303 assembledMatches[key] = entry
304 }
305
306 var response RequestAllMatchesResponseT
307 for _, match := range assembledMatches {
308 copied_match := match
309 response.MatchList = append(response.MatchList, &copied_match)
310 }
311
312 var MATCH_TYPE_ORDERING = []string{"qm", "ef", "qf", "sf", "f"}
313
314 err = nil
315 sort.Slice(response.MatchList, func(i, j int) bool {
316 if err != nil {
317 return false
318 }
319 a := response.MatchList[i]
320 b := response.MatchList[j]
321
Emily Markovaabcac6e2023-02-18 17:50:03 -0800322 aMatchTypeIndex, err2 := findIndexInList(MATCH_TYPE_ORDERING, a.CompLevel)
323 if err2 != nil {
324 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 -0800325 return false
326 }
Emily Markovaabcac6e2023-02-18 17:50:03 -0800327 bMatchTypeIndex, err2 := findIndexInList(MATCH_TYPE_ORDERING, b.CompLevel)
328 if err2 != nil {
329 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 -0800330 return false
331 }
332
333 if aMatchTypeIndex < bMatchTypeIndex {
334 return true
335 }
336 if aMatchTypeIndex > bMatchTypeIndex {
337 return false
338 }
339
340 // Then sort by match number. E.g. in semi finals, all match 1 rounds
341 // are done first. Then come match 2 rounds. And then, if necessary,
342 // the match 3 rounds.
343 aMatchNumber := a.MatchNumber
344 bMatchNumber := b.MatchNumber
345 if aMatchNumber < bMatchNumber {
346 return true
347 }
348 if aMatchNumber > bMatchNumber {
349 return false
350 }
351 // Lastly, sort by set number. I.e. Semi Final 1 Match 1 happens first.
352 // Then comes Semi Final 2 Match 1. Then comes Semi Final 1 Match 2. Then
353 // Semi Final 2 Match 2.
354 aSetNumber := a.SetNumber
355 bSetNumber := b.SetNumber
356 if aSetNumber < bSetNumber {
357 return true
358 }
359 if aSetNumber > bSetNumber {
360 return false
361 }
362 return true
363 })
364
365 if err != nil {
366 // check if error happened during sorting and notify webpage if that
367 respondWithError(w, http.StatusInternalServerError, fmt.Sprint(err))
368 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800369 }
370
371 builder := flatbuffers.NewBuilder(50 * 1024)
372 builder.Finish((&response).Pack(builder))
373 w.Write(builder.FinishedBytes())
374}
375
Alex Perry81f96ba2022-03-13 18:26:19 -0700376type submitNoteScoutingHandler struct {
377 db Database
378}
379
380func (handler submitNoteScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
381 requestBytes, err := io.ReadAll(req.Body)
382 if err != nil {
383 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
384 return
385 }
386
Philipp Schraderb7e75932022-03-26 16:18:34 -0700387 request, success := parseRequest(w, requestBytes, "SubmitNotes", submit_notes.GetRootAsSubmitNotes)
Alex Perry81f96ba2022-03-13 18:26:19 -0700388 if !success {
389 return
390 }
391
Filip Kujawaf947cb42022-11-21 10:00:30 -0800392 err = handler.db.AddNotes(db.NotesData{
Emily Markovae68b7632023-12-30 14:17:55 -0800393 TeamNumber: string(request.Team()),
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800394 Notes: string(request.Notes()),
395 GoodDriving: bool(request.GoodDriving()),
396 BadDriving: bool(request.BadDriving()),
Filip Kujawa11dc4c92023-04-13 08:55:43 -0700397 SolidPlacing: bool(request.SolidPlacing()),
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800398 SketchyPlacing: bool(request.SketchyPlacing()),
399 GoodDefense: bool(request.GoodDefense()),
400 BadDefense: bool(request.BadDefense()),
401 EasilyDefended: bool(request.EasilyDefended()),
Filip Kujawaf947cb42022-11-21 10:00:30 -0800402 })
Alex Perry81f96ba2022-03-13 18:26:19 -0700403 if err != nil {
404 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert notes: %v", err))
405 return
406 }
407
408 var response SubmitNotesResponseT
409 builder := flatbuffers.NewBuilder(10)
410 builder.Finish((&response).Pack(builder))
411 w.Write(builder.FinishedBytes())
412}
413
Emily Markovafaecfe12023-07-01 12:40:03 -0700414type submitPitImageScoutingHandler struct {
415 db Database
416}
417
418func (handler submitPitImageScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
419 requestBytes, err := io.ReadAll(req.Body)
420 if err != nil {
421 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
422 return
423 }
424
425 request, success := parseRequest(w, requestBytes, "SubmitPitImage", submit_pit_image.GetRootAsSubmitPitImage)
426 if !success {
427 return
428 }
429
430 err = handler.db.AddPitImage(db.PitImage{
431 TeamNumber: string(request.TeamNumber()),
432 CheckSum: db.ComputeSha256FromByteArray(request.ImageDataBytes()),
433 ImagePath: string(request.ImagePath()),
434 ImageData: request.ImageDataBytes(),
435 })
436 if err != nil {
437 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert notes: %v", err))
438 return
439 }
440
441 var response SubmitPitImageResponseT
442 builder := flatbuffers.NewBuilder(10)
443 builder.Finish((&response).Pack(builder))
444 w.Write(builder.FinishedBytes())
445}
446
Emily Markova8cb91312024-02-02 12:30:37 -0800447func ConvertActionsToStat2024(submit2024Actions *submit_2024_actions.Submit2024Actions) (db.Stats2024, error) {
448 overall_time := int64(0)
449 cycles := int64(0)
450 picked_up := false
451 lastPlacedTime := int64(0)
452 stat := db.Stats2024{
453 PreScouting: submit2024Actions.PreScouting(), TeamNumber: string(submit2024Actions.TeamNumber()), MatchNumber: submit2024Actions.MatchNumber(), SetNumber: submit2024Actions.SetNumber(), CompLevel: string(submit2024Actions.CompLevel()),
454 StartingQuadrant: 0, SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
455 Speaker: 0, Amp: 0, SpeakerAmplified: 0, AmpAmplified: 0, NotesDropped: 0, Penalties: 0,
456 TrapNote: false, AvgCycle: 0, Park: false, OnStage: false, Harmony: false, CollectedBy: "",
457 }
458 // Loop over all actions.
459 for i := 0; i < submit2024Actions.ActionsListLength(); i++ {
460 var action submit_2024_actions.Action
461 if !submit2024Actions.ActionsList(&action, i) {
462 return db.Stats2024{}, errors.New(fmt.Sprintf("Failed to parse submit_2024_actions.Action"))
463 }
464 actionTable := new(flatbuffers.Table)
465 action_type := action.ActionTakenType()
466 if !action.ActionTaken(actionTable) {
467 return db.Stats2024{}, errors.New(fmt.Sprint("Failed to parse sub-action or sub-action was missing"))
468 }
469 if action_type == submit_2024_actions.ActionTypeStartMatchAction {
470 var startMatchAction submit_2024_actions.StartMatchAction
471 startMatchAction.Init(actionTable.Bytes, actionTable.Pos)
472 stat.StartingQuadrant = startMatchAction.Position()
473 } else if action_type == submit_2024_actions.ActionTypeMobilityAction {
474 var mobilityAction submit_2024_actions.MobilityAction
475 mobilityAction.Init(actionTable.Bytes, actionTable.Pos)
476 if mobilityAction.Mobility() {
477 stat.MobilityAuto = true
478 }
479
480 } else if action_type == submit_2024_actions.ActionTypePenaltyAction {
481 var penaltyAction submit_2024_actions.PenaltyAction
482 penaltyAction.Init(actionTable.Bytes, actionTable.Pos)
483 stat.Penalties += 1
484
485 } else if action_type == submit_2024_actions.ActionTypePickupNoteAction {
486 var pick_up_action submit_2024_actions.PickupNoteAction
487 pick_up_action.Init(actionTable.Bytes, actionTable.Pos)
488 if picked_up == true {
489 auto := pick_up_action.Auto()
490 if auto == false {
491 stat.NotesDropped += 1
492 } else {
493 stat.NotesDroppedAuto += 1
494 }
495 } else {
496 picked_up = true
497 }
498 } else if action_type == submit_2024_actions.ActionTypePlaceNoteAction {
499 var place_action submit_2024_actions.PlaceNoteAction
500 place_action.Init(actionTable.Bytes, actionTable.Pos)
501 if !picked_up {
502 return db.Stats2024{}, errors.New(fmt.Sprintf("Got PlaceNoteAction without corresponding PickupObjectAction"))
503 }
504 score_type := place_action.ScoreType()
505 auto := place_action.Auto()
506 if score_type == submit_2024_actions.ScoreTypekAMP && auto {
507 stat.AmpAuto += 1
508 } else if score_type == submit_2024_actions.ScoreTypekAMP && !auto {
509 stat.Amp += 1
510 } else if score_type == submit_2024_actions.ScoreTypekAMP_AMPLIFIED && !auto {
511 stat.AmpAmplified += 1
512 } else if score_type == submit_2024_actions.ScoreTypekSPEAKER && !auto {
513 stat.Speaker += 1
514 } else if score_type == submit_2024_actions.ScoreTypekSPEAKER && auto {
515 stat.SpeakerAuto += 1
516 } else if score_type == submit_2024_actions.ScoreTypekSPEAKER_AMPLIFIED && !auto {
517 stat.SpeakerAmplified += 1
518 } else {
519 return db.Stats2024{}, errors.New(fmt.Sprintf("Got unknown ObjectType/ScoreLevel/Auto combination"))
520 }
521 picked_up = false
522 if lastPlacedTime != int64(0) {
523 // If this is not the first time we place,
524 // start counting cycle time. We define cycle
525 // time as the time between placements.
526 overall_time += int64(action.Timestamp()) - lastPlacedTime
527 cycles += 1
528 }
529 lastPlacedTime = int64(action.Timestamp())
530 } else if action_type == submit_2024_actions.ActionTypeEndMatchAction {
531 var endMatchAction submit_2024_actions.EndMatchAction
532 endMatchAction.Init(actionTable.Bytes, actionTable.Pos)
533 if endMatchAction.StageType() == submit_2024_actions.StageTypekON_STAGE {
534 stat.OnStage = true
535 } else if endMatchAction.StageType() == submit_2024_actions.StageTypekPARK {
536 stat.Park = true
537 } else if endMatchAction.StageType() == submit_2024_actions.StageTypekHARMONY {
538 stat.Harmony = true
539 }
540 stat.TrapNote = endMatchAction.TrapNote()
541 }
542 }
543 if cycles != 0 {
544 stat.AvgCycle = overall_time / cycles
545 } else {
546 stat.AvgCycle = 0
547 }
548 return stat, nil
549}
550
551// Handles a Request2024DataScouting request.
552type request2024DataScoutingHandler struct {
553 db Database
554}
555
556func (handler request2024DataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
557 requestBytes, err := io.ReadAll(req.Body)
558 if err != nil {
559 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
560 return
561 }
562
563 _, success := parseRequest(w, requestBytes, "Request2024DataScouting", request_2024_data_scouting.GetRootAsRequest2024DataScouting)
564 if !success {
565 return
566 }
567
568 stats, err := handler.db.ReturnStats2024()
569 if err != nil {
570 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
571 return
572 }
573
574 var response Request2024DataScoutingResponseT
575 for _, stat := range stats {
576 response.StatsList = append(response.StatsList, &request_2024_data_scouting_response.Stats2024T{
577 TeamNumber: stat.TeamNumber,
578 MatchNumber: stat.MatchNumber,
579 SetNumber: stat.SetNumber,
580 CompLevel: stat.CompLevel,
581 StartingQuadrant: stat.StartingQuadrant,
582 SpeakerAuto: stat.SpeakerAuto,
583 AmpAuto: stat.AmpAuto,
584 NotesDroppedAuto: stat.NotesDroppedAuto,
585 MobilityAuto: stat.MobilityAuto,
586 Speaker: stat.Speaker,
587 Amp: stat.Amp,
588 SpeakerAmplified: stat.SpeakerAmplified,
589 AmpAmplified: stat.AmpAmplified,
590 NotesDropped: stat.NotesDropped,
591 Penalties: stat.Penalties,
592 TrapNote: stat.TrapNote,
593 AvgCycle: stat.AvgCycle,
594 Park: stat.Park,
595 OnStage: stat.OnStage,
596 Harmony: stat.Harmony,
597 CollectedBy: stat.CollectedBy,
598 })
599 }
600
601 builder := flatbuffers.NewBuilder(50 * 1024)
602 builder.Finish((&response).Pack(builder))
603 w.Write(builder.FinishedBytes())
604}
605
Emily Markova1abe9782023-03-11 19:45:38 -0800606func ConvertActionsToStat(submitActions *submit_actions.SubmitActions) (db.Stats2023, error) {
607 overall_time := int64(0)
608 cycles := int64(0)
609 picked_up := false
610 lastPlacedTime := int64(0)
Philipp Schrader4b489222023-04-15 16:40:16 -0700611 stat := db.Stats2023{
612 PreScouting: submitActions.PreScouting(),
613 TeamNumber: string(submitActions.TeamNumber()), MatchNumber: submitActions.MatchNumber(), SetNumber: submitActions.SetNumber(), CompLevel: string(submitActions.CompLevel()),
Emily Markova1abe9782023-03-11 19:45:38 -0800614 StartingQuadrant: 0, LowCubesAuto: 0, MiddleCubesAuto: 0, HighCubesAuto: 0, CubesDroppedAuto: 0,
615 LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 0, ConesDroppedAuto: 0, LowCubes: 0, MiddleCubes: 0, HighCubes: 0,
Philipp Schradere11114f2023-04-15 17:04:25 -0700616 CubesDropped: 0, LowCones: 0, MiddleCones: 0, HighCones: 0, ConesDropped: 0, SuperchargedPieces: 0, AvgCycle: 0, CollectedBy: "",
Emily Markova1abe9782023-03-11 19:45:38 -0800617 }
618 // Loop over all actions.
619 for i := 0; i < submitActions.ActionsListLength(); i++ {
620 var action submit_actions.Action
621 if !submitActions.ActionsList(&action, i) {
622 return db.Stats2023{}, errors.New(fmt.Sprintf("Failed to parse submit_actions.Action"))
623 }
624 actionTable := new(flatbuffers.Table)
625 action_type := action.ActionTakenType()
626 if !action.ActionTaken(actionTable) {
627 return db.Stats2023{}, errors.New(fmt.Sprint("Failed to parse sub-action or sub-action was missing"))
628 }
629 if action_type == submit_actions.ActionTypeStartMatchAction {
630 var startMatchAction submit_actions.StartMatchAction
631 startMatchAction.Init(actionTable.Bytes, actionTable.Pos)
632 stat.StartingQuadrant = startMatchAction.Position()
Filip Kujawa0b4b1e52023-04-15 14:05:40 -0700633 } else if action_type == submit_actions.ActionTypeMobilityAction {
634 var mobilityAction submit_actions.MobilityAction
635 mobilityAction.Init(actionTable.Bytes, actionTable.Pos)
636 if mobilityAction.Mobility() {
637 stat.Mobility = true
638 }
639
Emily Markova46a69bf2023-03-22 20:45:52 -0700640 } else if action_type == submit_actions.ActionTypeAutoBalanceAction {
641 var autoBalanceAction submit_actions.AutoBalanceAction
642 autoBalanceAction.Init(actionTable.Bytes, actionTable.Pos)
643 if autoBalanceAction.Docked() {
644 stat.DockedAuto = true
645 }
646 if autoBalanceAction.Engaged() {
647 stat.EngagedAuto = true
648 }
Emily Markova63c63f62023-03-29 20:57:35 -0700649 if autoBalanceAction.BalanceAttempt() {
650 stat.BalanceAttemptAuto = true
651 }
Emily Markova1abe9782023-03-11 19:45:38 -0800652 } else if action_type == submit_actions.ActionTypePickupObjectAction {
653 var pick_up_action submit_actions.PickupObjectAction
654 pick_up_action.Init(actionTable.Bytes, actionTable.Pos)
655 if picked_up == true {
656 object := pick_up_action.ObjectType().String()
657 auto := pick_up_action.Auto()
658 if object == "kCube" && auto == false {
659 stat.CubesDropped += 1
660 } else if object == "kCube" && auto == true {
661 stat.CubesDroppedAuto += 1
662 } else if object == "kCone" && auto == false {
663 stat.ConesDropped += 1
664 } else if object == "kCube" && auto == true {
665 stat.ConesDroppedAuto += 1
666 }
667 } else {
668 picked_up = true
669 }
670 } else if action_type == submit_actions.ActionTypePlaceObjectAction {
671 var place_action submit_actions.PlaceObjectAction
672 place_action.Init(actionTable.Bytes, actionTable.Pos)
673 if !picked_up {
674 return db.Stats2023{}, errors.New(fmt.Sprintf("Got PlaceObjectAction without corresponding PickupObjectAction"))
675 }
676 object := place_action.ObjectType()
677 level := place_action.ScoreLevel()
678 auto := place_action.Auto()
679 if object == 0 && level == 0 && auto == true {
680 stat.LowCubesAuto += 1
681 } else if object == 0 && level == 0 && auto == false {
682 stat.LowCubes += 1
683 } else if object == 0 && level == 1 && auto == true {
684 stat.MiddleCubesAuto += 1
685 } else if object == 0 && level == 1 && auto == false {
686 stat.MiddleCubes += 1
687 } else if object == 0 && level == 2 && auto == true {
688 stat.HighCubesAuto += 1
689 } else if object == 0 && level == 2 && auto == false {
690 stat.HighCubes += 1
691 } else if object == 1 && level == 0 && auto == true {
692 stat.LowConesAuto += 1
693 } else if object == 1 && level == 0 && auto == false {
694 stat.LowCones += 1
695 } else if object == 1 && level == 1 && auto == true {
696 stat.MiddleConesAuto += 1
697 } else if object == 1 && level == 1 && auto == false {
698 stat.MiddleCones += 1
699 } else if object == 1 && level == 2 && auto == true {
700 stat.HighConesAuto += 1
701 } else if object == 1 && level == 2 && auto == false {
702 stat.HighCones += 1
Filip Kujawa7a045e72023-04-13 08:41:09 -0700703 } else if level == 3 {
704 stat.SuperchargedPieces += 1
Emily Markova1abe9782023-03-11 19:45:38 -0800705 } else {
706 return db.Stats2023{}, errors.New(fmt.Sprintf("Got unknown ObjectType/ScoreLevel/Auto combination"))
707 }
708 picked_up = false
709 if lastPlacedTime != int64(0) {
710 // If this is not the first time we place,
711 // start counting cycle time. We define cycle
712 // time as the time between placements.
713 overall_time += int64(action.Timestamp()) - lastPlacedTime
714 cycles += 1
715 }
716 lastPlacedTime = int64(action.Timestamp())
Emily Markova46a69bf2023-03-22 20:45:52 -0700717 } else if action_type == submit_actions.ActionTypeEndMatchAction {
718 var endMatchAction submit_actions.EndMatchAction
719 endMatchAction.Init(actionTable.Bytes, actionTable.Pos)
720 if endMatchAction.Docked() {
721 stat.Docked = true
722 }
723 if endMatchAction.Engaged() {
724 stat.Engaged = true
725 }
Emily Markova63c63f62023-03-29 20:57:35 -0700726 if endMatchAction.BalanceAttempt() {
727 stat.BalanceAttempt = true
728 }
Emily Markova1abe9782023-03-11 19:45:38 -0800729 }
730 }
731 if cycles != 0 {
Philipp Schrader8c878a22023-03-20 22:36:38 -0700732 stat.AvgCycle = overall_time / cycles
Emily Markova1abe9782023-03-11 19:45:38 -0800733 } else {
734 stat.AvgCycle = 0
735 }
736 return stat, nil
737}
738
Emily Markova290147d2023-03-03 22:40:06 -0800739// Handles a Request2023DataScouting request.
740type request2023DataScoutingHandler struct {
741 db Database
742}
743
744func (handler request2023DataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
745 requestBytes, err := io.ReadAll(req.Body)
746 if err != nil {
747 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
748 return
749 }
750
751 _, success := parseRequest(w, requestBytes, "Request2023DataScouting", request_2023_data_scouting.GetRootAsRequest2023DataScouting)
752 if !success {
753 return
754 }
755
756 stats, err := handler.db.ReturnStats2023()
757 if err != nil {
758 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
759 return
760 }
761
762 var response Request2023DataScoutingResponseT
763 for _, stat := range stats {
764 response.StatsList = append(response.StatsList, &request_2023_data_scouting_response.Stats2023T{
Emily Markova63c63f62023-03-29 20:57:35 -0700765 TeamNumber: stat.TeamNumber,
766 MatchNumber: stat.MatchNumber,
767 SetNumber: stat.SetNumber,
768 CompLevel: stat.CompLevel,
769 StartingQuadrant: stat.StartingQuadrant,
770 LowCubesAuto: stat.LowCubesAuto,
771 MiddleCubesAuto: stat.MiddleCubesAuto,
772 HighCubesAuto: stat.HighCubesAuto,
773 CubesDroppedAuto: stat.CubesDroppedAuto,
774 LowConesAuto: stat.LowConesAuto,
775 MiddleConesAuto: stat.MiddleConesAuto,
776 HighConesAuto: stat.HighConesAuto,
777 ConesDroppedAuto: stat.ConesDroppedAuto,
778 LowCubes: stat.LowCubes,
779 MiddleCubes: stat.MiddleCubes,
780 HighCubes: stat.HighCubes,
781 CubesDropped: stat.CubesDropped,
782 LowCones: stat.LowCones,
783 MiddleCones: stat.MiddleCones,
784 HighCones: stat.HighCones,
785 ConesDropped: stat.ConesDropped,
Filip Kujawa7a045e72023-04-13 08:41:09 -0700786 SuperchargedPieces: stat.SuperchargedPieces,
Emily Markova63c63f62023-03-29 20:57:35 -0700787 AvgCycle: stat.AvgCycle,
Filip Kujawa0b4b1e52023-04-15 14:05:40 -0700788 Mobility: stat.Mobility,
Emily Markova63c63f62023-03-29 20:57:35 -0700789 DockedAuto: stat.DockedAuto,
790 EngagedAuto: stat.EngagedAuto,
791 BalanceAttemptAuto: stat.BalanceAttemptAuto,
792 Docked: stat.Docked,
793 Engaged: stat.Engaged,
794 BalanceAttempt: stat.BalanceAttempt,
795 CollectedBy: stat.CollectedBy,
Emily Markova290147d2023-03-03 22:40:06 -0800796 })
797 }
798
799 builder := flatbuffers.NewBuilder(50 * 1024)
800 builder.Finish((&response).Pack(builder))
801 w.Write(builder.FinishedBytes())
802}
803
Emily Markova8e39f452023-12-23 12:17:30 -0800804type requestAllPitImagesHandler struct {
805 db Database
806}
807
808func (handler requestAllPitImagesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
809 requestBytes, err := io.ReadAll(req.Body)
810 if err != nil {
811 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
812 return
813 }
814
815 _, success := parseRequest(w, requestBytes, "RequestAllPitImages", request_all_pit_images.GetRootAsRequestAllPitImages)
816 if !success {
817 return
818 }
819
820 images, err := handler.db.ReturnPitImages()
821 if err != nil {
822 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get pit images: %v", err))
823 return
824 }
825
826 var response RequestAllPitImagesResponseT
827 for _, data := range images {
828 response.PitImageList = append(response.PitImageList, &request_all_pit_images_response.PitImageT{
829 TeamNumber: data.TeamNumber,
830 ImagePath: data.ImagePath,
831 CheckSum: data.CheckSum,
832 })
833 }
834
835 builder := flatbuffers.NewBuilder(1024)
836 builder.Finish((&response).Pack(builder))
837 w.Write(builder.FinishedBytes())
838}
839
Emily Markovafaecfe12023-07-01 12:40:03 -0700840type requestPitImagesHandler struct {
841 db Database
842}
843
844func (handler requestPitImagesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
845 requestBytes, err := io.ReadAll(req.Body)
846 if err != nil {
847 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
848 return
849 }
850
851 request, success := parseRequest(w, requestBytes, "RequestPitImages", request_pit_images.GetRootAsRequestPitImages)
852 if !success {
853 return
854 }
855
856 images, err := handler.db.QueryPitImages(string(request.TeamNumber()))
857 if err != nil {
858 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query pit images: %v", err))
859 return
860 }
861
862 var response RequestPitImagesResponseT
863 for _, data := range images {
864 response.PitImageList = append(response.PitImageList, &request_pit_images_response.PitImageT{
865 TeamNumber: data.TeamNumber,
866 ImagePath: data.ImagePath,
867 CheckSum: data.CheckSum,
868 })
869 }
870
871 builder := flatbuffers.NewBuilder(1024)
872 builder.Finish((&response).Pack(builder))
873 w.Write(builder.FinishedBytes())
874}
875
Alex Perry81f96ba2022-03-13 18:26:19 -0700876type requestNotesForTeamHandler struct {
877 db Database
878}
879
880func (handler requestNotesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
881 requestBytes, err := io.ReadAll(req.Body)
882 if err != nil {
883 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
884 return
885 }
886
Philipp Schraderb7e75932022-03-26 16:18:34 -0700887 request, success := parseRequest(w, requestBytes, "RequestNotesForTeam", request_notes_for_team.GetRootAsRequestNotesForTeam)
Alex Perry81f96ba2022-03-13 18:26:19 -0700888 if !success {
889 return
890 }
891
Emily Markovae68b7632023-12-30 14:17:55 -0800892 notes, err := handler.db.QueryNotes(string(request.Team()))
Alex Perry81f96ba2022-03-13 18:26:19 -0700893 if err != nil {
894 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query notes: %v", err))
895 return
896 }
897
898 var response RequestNotesForTeamResponseT
Philipp Schradereecb8962022-06-01 21:02:42 -0700899 for _, data := range notes {
Alex Perry81f96ba2022-03-13 18:26:19 -0700900 response.Notes = append(response.Notes, &request_notes_for_team_response.NoteT{data})
901 }
902
903 builder := flatbuffers.NewBuilder(1024)
904 builder.Finish((&response).Pack(builder))
905 w.Write(builder.FinishedBytes())
906}
907
Milo Lin1d59f0c2022-06-22 20:30:58 -0700908type requestShiftScheduleHandler struct {
909 db Database
910}
911
912func (handler requestShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
913 requestBytes, err := io.ReadAll(req.Body)
914 if err != nil {
915 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
916 return
917 }
918
919 _, success := parseRequest(w, requestBytes, "RequestShiftSchedule", request_shift_schedule.GetRootAsRequestShiftSchedule)
920 if !success {
921 return
922 }
923
924 shiftData, err := handler.db.ReturnAllShifts()
925 if err != nil {
926 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query shift schedule: %v", err))
927 return
928 }
929
930 var response RequestShiftScheduleResponseT
931 for _, shifts := range shiftData {
932 response.ShiftSchedule = append(response.ShiftSchedule, &request_shift_schedule_response.MatchAssignmentT{
933 MatchNumber: shifts.MatchNumber,
Philipp Schrader2ff455b2023-05-03 22:11:50 -0700934 R1Scouter: shifts.R1scouter,
935 R2Scouter: shifts.R2scouter,
936 R3Scouter: shifts.R3scouter,
937 B1Scouter: shifts.B1scouter,
938 B2Scouter: shifts.B2scouter,
939 B3Scouter: shifts.B3scouter,
Milo Lin1d59f0c2022-06-22 20:30:58 -0700940 })
941 }
942
943 builder := flatbuffers.NewBuilder(1024)
944 builder.Finish((&response).Pack(builder))
945 w.Write(builder.FinishedBytes())
946}
947
948type submitShiftScheduleHandler struct {
949 db Database
950}
951
952func (handler submitShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
953 // Get the username of the person submitting the data.
954 username := parseUsername(req)
955
956 requestBytes, err := io.ReadAll(req.Body)
957 if err != nil {
958 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
959 return
960 }
961
962 request, success := parseRequest[SubmitShiftSchedule](w, requestBytes, "SubmitShiftSchedule", submit_shift_schedule.GetRootAsSubmitShiftSchedule)
963 if !success {
964 return
965 }
966
967 log.Println("Got shift schedule from", username)
968 shift_schedule_length := request.ShiftScheduleLength()
969 for i := 0; i < shift_schedule_length; i++ {
970 var match_assignment submit_shift_schedule.MatchAssignment
971 request.ShiftSchedule(&match_assignment, i)
972 current_shift := db.Shift{
973 MatchNumber: match_assignment.MatchNumber(),
Philipp Schrader2ff455b2023-05-03 22:11:50 -0700974 R1scouter: string(match_assignment.R1Scouter()),
975 R2scouter: string(match_assignment.R2Scouter()),
976 R3scouter: string(match_assignment.R3Scouter()),
977 B1scouter: string(match_assignment.B1Scouter()),
978 B2scouter: string(match_assignment.B2Scouter()),
979 B3scouter: string(match_assignment.B3Scouter()),
Milo Lin1d59f0c2022-06-22 20:30:58 -0700980 }
981 err = handler.db.AddToShift(current_shift)
982 if err != nil {
983 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit shift schedule: ", err))
984 return
985 }
986 }
987
988 builder := flatbuffers.NewBuilder(50 * 1024)
989 builder.Finish((&SubmitShiftScheduleResponseT{}).Pack(builder))
990 w.Write(builder.FinishedBytes())
991}
992
Filip Kujawa210a03b2022-11-24 14:41:11 -0800993type SubmitDriverRankingHandler struct {
994 db Database
995}
996
997func (handler SubmitDriverRankingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
998 requestBytes, err := io.ReadAll(req.Body)
999 if err != nil {
1000 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
1001 return
1002 }
1003
1004 request, success := parseRequest(w, requestBytes, "SubmitDriverRanking", submit_driver_ranking.GetRootAsSubmitDriverRanking)
1005 if !success {
1006 return
1007 }
1008
1009 err = handler.db.AddDriverRanking(db.DriverRankingData{
1010 MatchNumber: request.MatchNumber(),
Emily Markovae68b7632023-12-30 14:17:55 -08001011 Rank1: string(request.Rank1()),
1012 Rank2: string(request.Rank2()),
1013 Rank3: string(request.Rank3()),
Filip Kujawa210a03b2022-11-24 14:41:11 -08001014 })
1015
1016 if err != nil {
1017 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert driver ranking: %v", err))
1018 return
1019 }
1020
1021 var response SubmitDriverRankingResponseT
1022 builder := flatbuffers.NewBuilder(10)
1023 builder.Finish((&response).Pack(builder))
1024 w.Write(builder.FinishedBytes())
1025}
1026
Filip Kujawaf882e022022-12-14 13:14:08 -08001027type requestAllNotesHandler struct {
1028 db Database
1029}
1030
1031func (handler requestAllNotesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
1032 requestBytes, err := io.ReadAll(req.Body)
1033 if err != nil {
1034 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
1035 return
1036 }
1037
1038 _, success := parseRequest(w, requestBytes, "RequestAllNotes", request_all_notes.GetRootAsRequestAllNotes)
1039 if !success {
1040 return
1041 }
1042
1043 notes, err := handler.db.ReturnAllNotes()
1044 if err != nil {
1045 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
1046 return
1047 }
1048
1049 var response RequestAllNotesResponseT
1050 for _, note := range notes {
1051 response.NoteList = append(response.NoteList, &request_all_notes_response.NoteT{
Filip Kujawa7ddd5652023-03-07 19:56:15 -08001052 Team: note.TeamNumber,
1053 Notes: note.Notes,
1054 GoodDriving: note.GoodDriving,
1055 BadDriving: note.BadDriving,
Filip Kujawa11dc4c92023-04-13 08:55:43 -07001056 SolidPlacing: note.SolidPlacing,
Filip Kujawa7ddd5652023-03-07 19:56:15 -08001057 SketchyPlacing: note.SketchyPlacing,
1058 GoodDefense: note.GoodDefense,
1059 BadDefense: note.BadDefense,
1060 EasilyDefended: note.EasilyDefended,
Filip Kujawaf882e022022-12-14 13:14:08 -08001061 })
1062 }
1063
1064 builder := flatbuffers.NewBuilder(50 * 1024)
1065 builder.Finish((&response).Pack(builder))
1066 w.Write(builder.FinishedBytes())
1067}
1068
1069type requestAllDriverRankingsHandler struct {
1070 db Database
1071}
1072
1073func (handler requestAllDriverRankingsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
1074 requestBytes, err := io.ReadAll(req.Body)
1075 if err != nil {
1076 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
1077 return
1078 }
1079
1080 _, success := parseRequest(w, requestBytes, "RequestAllDriverRankings", request_all_driver_rankings.GetRootAsRequestAllDriverRankings)
1081 if !success {
1082 return
1083 }
1084
1085 rankings, err := handler.db.ReturnAllDriverRankings()
1086 if err != nil {
1087 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
1088 return
1089 }
1090
1091 var response RequestAllDriverRankingsResponseT
1092 for _, ranking := range rankings {
1093 response.DriverRankingList = append(response.DriverRankingList, &request_all_driver_rankings_response.RankingT{
1094 MatchNumber: ranking.MatchNumber,
1095 Rank1: ranking.Rank1,
1096 Rank2: ranking.Rank2,
1097 Rank3: ranking.Rank3,
1098 })
1099 }
1100
1101 builder := flatbuffers.NewBuilder(50 * 1024)
1102 builder.Finish((&response).Pack(builder))
1103 w.Write(builder.FinishedBytes())
1104}
1105
Emily Markova8cb91312024-02-02 12:30:37 -08001106type submit2024ActionsHandler struct {
1107 db Database
1108}
1109
1110func (handler submit2024ActionsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
1111 // Get the username of the person submitting the data.
1112 username := parseUsername(req)
1113
1114 requestBytes, err := io.ReadAll(req.Body)
1115 if err != nil {
1116 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
1117 return
1118 }
1119
1120 request, success := parseRequest(w, requestBytes, "Submit2024Actions", submit_2024_actions.GetRootAsSubmit2024Actions)
1121 if !success {
1122 return
1123 }
1124
1125 log.Println("Got actions for match", request.MatchNumber(), "team", request.TeamNumber(), "from", username)
1126
1127 for i := 0; i < request.ActionsListLength(); i++ {
1128
1129 var action Action2024
1130 request.ActionsList(&action, i)
1131
1132 dbAction := db.Action{
1133 PreScouting: request.PreScouting(),
1134 TeamNumber: string(request.TeamNumber()),
1135 MatchNumber: request.MatchNumber(),
1136 SetNumber: request.SetNumber(),
1137 CompLevel: string(request.CompLevel()),
1138 //TODO: Serialize CompletedAction
1139 CompletedAction: []byte{},
1140 Timestamp: action.Timestamp(),
1141 CollectedBy: username,
1142 }
1143
1144 // Do some error checking.
1145 if action.Timestamp() < 0 {
1146 respondWithError(w, http.StatusBadRequest, fmt.Sprint(
1147 "Invalid timestamp field value of ", action.Timestamp()))
1148 return
1149 }
1150
1151 err = handler.db.AddAction(dbAction)
1152 if err != nil {
1153 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to add action to database: ", err))
1154 return
1155 }
1156 }
1157
1158 stats, err := ConvertActionsToStat2024(request)
1159 if err != nil {
1160 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to convert actions to stats: ", err))
1161 return
1162 }
1163
1164 stats.CollectedBy = username
1165
1166 err = handler.db.AddToStats2024(stats)
1167 if err != nil {
1168 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit stats: ", stats, ": ", err))
1169 return
1170 }
1171
1172 builder := flatbuffers.NewBuilder(50 * 1024)
1173 builder.Finish((&SubmitActionsResponseT{}).Pack(builder))
1174 w.Write(builder.FinishedBytes())
1175}
1176
Sabina Leaver9b4eb312023-02-20 19:58:17 -08001177type submitActionsHandler struct {
1178 db Database
1179}
1180
1181func (handler submitActionsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
1182 // Get the username of the person submitting the data.
1183 username := parseUsername(req)
1184
1185 requestBytes, err := io.ReadAll(req.Body)
1186 if err != nil {
1187 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
1188 return
1189 }
1190
1191 request, success := parseRequest(w, requestBytes, "SubmitActions", submit_actions.GetRootAsSubmitActions)
1192 if !success {
1193 return
1194 }
1195
1196 log.Println("Got actions for match", request.MatchNumber(), "team", request.TeamNumber(), "from", username)
1197
1198 for i := 0; i < request.ActionsListLength(); i++ {
1199
1200 var action Action
1201 request.ActionsList(&action, i)
1202
1203 dbAction := db.Action{
Philipp Schrader4b489222023-04-15 16:40:16 -07001204 PreScouting: request.PreScouting(),
Sabina Leaver9b4eb312023-02-20 19:58:17 -08001205 TeamNumber: string(request.TeamNumber()),
1206 MatchNumber: request.MatchNumber(),
1207 SetNumber: request.SetNumber(),
1208 CompLevel: string(request.CompLevel()),
1209 //TODO: Serialize CompletedAction
1210 CompletedAction: []byte{},
Philipp Schrader670a1c82023-05-17 19:42:43 -07001211 Timestamp: action.Timestamp(),
Sabina Leaver9b4eb312023-02-20 19:58:17 -08001212 CollectedBy: username,
1213 }
1214
1215 // Do some error checking.
1216 if action.Timestamp() < 0 {
1217 respondWithError(w, http.StatusBadRequest, fmt.Sprint(
1218 "Invalid timestamp field value of ", action.Timestamp()))
1219 return
1220 }
1221
1222 err = handler.db.AddAction(dbAction)
1223 if err != nil {
1224 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to add action to database: ", err))
1225 return
1226 }
1227 }
1228
Philipp Schradere11114f2023-04-15 17:04:25 -07001229 stats, err := ConvertActionsToStat(request)
1230 if err != nil {
1231 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to convert actions to stats: ", err))
1232 return
1233 }
1234
1235 stats.CollectedBy = username
1236
1237 err = handler.db.AddToStats2023(stats)
1238 if err != nil {
1239 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit stats: ", stats, ": ", err))
1240 return
1241 }
1242
Sabina Leaver9b4eb312023-02-20 19:58:17 -08001243 builder := flatbuffers.NewBuilder(50 * 1024)
1244 builder.Finish((&SubmitActionsResponseT{}).Pack(builder))
1245 w.Write(builder.FinishedBytes())
1246}
1247
Emily Markova8cb91312024-02-02 12:30:37 -08001248type Delete2024DataScoutingHandler struct {
1249 db Database
1250}
1251
1252func (handler Delete2024DataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
1253 requestBytes, err := io.ReadAll(req.Body)
1254 if err != nil {
1255 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
1256 return
1257 }
1258
1259 request, success := parseRequest(w, requestBytes, "Delete2024DataScouting", delete_2024_data_scouting.GetRootAsDelete2024DataScouting)
1260 if !success {
1261 return
1262 }
1263
1264 err = handler.db.DeleteFromStats2024(
1265 string(request.CompLevel()),
1266 request.MatchNumber(),
1267 request.SetNumber(),
1268 string(request.TeamNumber()))
1269
1270 if err != nil {
1271 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to delete from stats2024: %v", err))
1272 return
1273 }
1274
1275 err = handler.db.DeleteFromActions(
1276 string(request.CompLevel()),
1277 request.MatchNumber(),
1278 request.SetNumber(),
1279 string(request.TeamNumber()))
1280
1281 if err != nil {
1282 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to delete from actions: %v", err))
1283 return
1284 }
1285
1286 var response Delete2024DataScoutingResponseT
1287 builder := flatbuffers.NewBuilder(10)
1288 builder.Finish((&response).Pack(builder))
1289 w.Write(builder.FinishedBytes())
1290}
1291
Filip Kujawac1ded372023-05-27 14:33:43 -07001292type Delete2023DataScoutingHandler struct {
1293 db Database
1294}
1295
1296func (handler Delete2023DataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
1297 requestBytes, err := io.ReadAll(req.Body)
1298 if err != nil {
1299 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
1300 return
1301 }
1302
1303 request, success := parseRequest(w, requestBytes, "Delete2023DataScouting", delete_2023_data_scouting.GetRootAsDelete2023DataScouting)
1304 if !success {
1305 return
1306 }
1307
1308 err = handler.db.DeleteFromStats(
1309 string(request.CompLevel()),
1310 request.MatchNumber(),
1311 request.SetNumber(),
1312 string(request.TeamNumber()))
1313
1314 if err != nil {
1315 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to delete from stats: %v", err))
1316 return
1317 }
1318
1319 err = handler.db.DeleteFromActions(
1320 string(request.CompLevel()),
1321 request.MatchNumber(),
1322 request.SetNumber(),
1323 string(request.TeamNumber()))
1324
1325 if err != nil {
1326 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to delete from actions: %v", err))
1327 return
1328 }
1329
1330 var response Delete2023DataScoutingResponseT
1331 builder := flatbuffers.NewBuilder(10)
1332 builder.Finish((&response).Pack(builder))
1333 w.Write(builder.FinishedBytes())
1334}
1335
Philipp Schrader43c730b2023-02-26 20:27:44 -08001336func HandleRequests(db Database, scoutingServer server.ScoutingServer) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08001337 scoutingServer.HandleFunc("/requests", unknown)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -08001338 scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
Filip Kujawaf882e022022-12-14 13:14:08 -08001339 scoutingServer.Handle("/requests/request/all_notes", requestAllNotesHandler{db})
1340 scoutingServer.Handle("/requests/request/all_driver_rankings", requestAllDriverRankingsHandler{db})
Emily Markova290147d2023-03-03 22:40:06 -08001341 scoutingServer.Handle("/requests/request/2023_data_scouting", request2023DataScoutingHandler{db})
Emily Markova8cb91312024-02-02 12:30:37 -08001342 scoutingServer.Handle("/requests/request/2024_data_scouting", request2024DataScoutingHandler{db})
Alex Perry81f96ba2022-03-13 18:26:19 -07001343 scoutingServer.Handle("/requests/submit/submit_notes", submitNoteScoutingHandler{db})
Emily Markovafaecfe12023-07-01 12:40:03 -07001344 scoutingServer.Handle("/requests/submit/submit_pit_image", submitPitImageScoutingHandler{db})
1345 scoutingServer.Handle("/requests/request/pit_images", requestPitImagesHandler{db})
Emily Markova8e39f452023-12-23 12:17:30 -08001346 scoutingServer.Handle("/requests/request/all_pit_images", requestAllPitImagesHandler{db})
Alex Perry81f96ba2022-03-13 18:26:19 -07001347 scoutingServer.Handle("/requests/request/notes_for_team", requestNotesForTeamHandler{db})
Milo Lin1d59f0c2022-06-22 20:30:58 -07001348 scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
1349 scoutingServer.Handle("/requests/request/shift_schedule", requestShiftScheduleHandler{db})
Filip Kujawa210a03b2022-11-24 14:41:11 -08001350 scoutingServer.Handle("/requests/submit/submit_driver_ranking", SubmitDriverRankingHandler{db})
Sabina Leaver9b4eb312023-02-20 19:58:17 -08001351 scoutingServer.Handle("/requests/submit/submit_actions", submitActionsHandler{db})
Emily Markova8cb91312024-02-02 12:30:37 -08001352 scoutingServer.Handle("/requests/submit/submit_2024_actions", submit2024ActionsHandler{db})
Filip Kujawac1ded372023-05-27 14:33:43 -07001353 scoutingServer.Handle("/requests/delete/delete_2023_data_scouting", Delete2023DataScoutingHandler{db})
Emily Markova8cb91312024-02-02 12:30:37 -08001354 scoutingServer.Handle("/requests/delete/delete_2024_data_scouting", Delete2024DataScoutingHandler{db})
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08001355}