blob: 6a35e20d6230dbf7a9230fe6365a19f735942fba [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"
Emily Markova521725a2024-03-21 18:46:04 -070013 "time"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080014
Philipp Schrader8747f1b2022-02-23 23:56:22 -080015 "github.com/frc971/971-Robot-Code/scouting/db"
Emily Markova8cb91312024-02-02 12:30:37 -080016 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/delete_2024_data_scouting"
17 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/delete_2024_data_scouting_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080018 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
Emily Markova8cb91312024-02-02 12:30:37 -080019 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_2024_data_scouting"
20 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_2024_data_scouting_response"
Filip Kujawaf882e022022-12-14 13:14:08 -080021 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_driver_rankings"
22 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_driver_rankings_response"
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080023 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches"
24 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches_response"
Filip Kujawaf882e022022-12-14 13:14:08 -080025 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_notes"
26 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_notes_response"
Emily Markova8e39f452023-12-23 12:17:30 -080027 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_pit_images"
28 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_pit_images_response"
Emily Markova521725a2024-03-21 18:46:04 -070029 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_current_scouting"
30 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_current_scouting_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070031 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team"
32 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team_response"
Emily Markovafaecfe12023-07-01 12:40:03 -070033 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_pit_images"
34 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_pit_images_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070035 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule"
36 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule_response"
Emily Markova8cb91312024-02-02 12:30:37 -080037 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_2024_actions"
38 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_2024_actions_response"
Filip Kujawa210a03b2022-11-24 14:41:11 -080039 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking"
40 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070041 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes"
42 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
Emily Markovafaecfe12023-07-01 12:40:03 -070043 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_pit_image"
44 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_pit_image_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070045 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule"
46 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080047 "github.com/frc971/971-Robot-Code/scouting/webserver/server"
48 flatbuffers "github.com/google/flatbuffers/go"
49)
50
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080051type RequestAllMatches = request_all_matches.RequestAllMatches
52type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
Filip Kujawaf882e022022-12-14 13:14:08 -080053type RequestAllDriverRankings = request_all_driver_rankings.RequestAllDriverRankings
54type RequestAllDriverRankingsResponseT = request_all_driver_rankings_response.RequestAllDriverRankingsResponseT
55type RequestAllNotes = request_all_notes.RequestAllNotes
56type RequestAllNotesResponseT = request_all_notes_response.RequestAllNotesResponseT
Emily Markova8cb91312024-02-02 12:30:37 -080057type Request2024DataScouting = request_2024_data_scouting.Request2024DataScouting
58type Request2024DataScoutingResponseT = request_2024_data_scouting_response.Request2024DataScoutingResponseT
Alex Perry81f96ba2022-03-13 18:26:19 -070059type SubmitNotes = submit_notes.SubmitNotes
60type SubmitNotesResponseT = submit_notes_response.SubmitNotesResponseT
Emily Markovafaecfe12023-07-01 12:40:03 -070061type SubmitPitImage = submit_pit_image.SubmitPitImage
62type SubmitPitImageResponseT = submit_pit_image_response.SubmitPitImageResponseT
63type RequestPitImages = request_pit_images.RequestPitImages
64type RequestPitImagesResponseT = request_pit_images_response.RequestPitImagesResponseT
Emily Markova8e39f452023-12-23 12:17:30 -080065type RequestAllPitImages = request_all_pit_images.RequestAllPitImages
66type RequestAllPitImagesResponseT = request_all_pit_images_response.RequestAllPitImagesResponseT
Emily Markova521725a2024-03-21 18:46:04 -070067type RequestCurrentScouting = request_current_scouting.RequestCurrentScouting
68type RequestCurrentScoutingResponseT = request_current_scouting_response.RequestCurrentScoutingResponseT
Alex Perry81f96ba2022-03-13 18:26:19 -070069type RequestNotesForTeam = request_notes_for_team.RequestNotesForTeam
70type RequestNotesForTeamResponseT = request_notes_for_team_response.RequestNotesForTeamResponseT
Milo Lin1d59f0c2022-06-22 20:30:58 -070071type RequestShiftSchedule = request_shift_schedule.RequestShiftSchedule
72type RequestShiftScheduleResponseT = request_shift_schedule_response.RequestShiftScheduleResponseT
73type SubmitShiftSchedule = submit_shift_schedule.SubmitShiftSchedule
74type SubmitShiftScheduleResponseT = submit_shift_schedule_response.SubmitShiftScheduleResponseT
Filip Kujawa210a03b2022-11-24 14:41:11 -080075type SubmitDriverRanking = submit_driver_ranking.SubmitDriverRanking
76type SubmitDriverRankingResponseT = submit_driver_ranking_response.SubmitDriverRankingResponseT
Emily Markova8cb91312024-02-02 12:30:37 -080077type Action2024 = submit_2024_actions.Action
Emily Markova8cb91312024-02-02 12:30:37 -080078type Submit2024Actions = submit_2024_actions.Submit2024Actions
79type Submit2024ActionsResponseT = submit_2024_actions_response.Submit2024ActionsResponseT
Emily Markova8cb91312024-02-02 12:30:37 -080080type Delete2024DataScouting = delete_2024_data_scouting.Delete2024DataScouting
81type Delete2024DataScoutingResponseT = delete_2024_data_scouting_response.Delete2024DataScoutingResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080082
Philipp Schrader8747f1b2022-02-23 23:56:22 -080083// The interface we expect the database abstraction to conform to.
84// We use an interface here because it makes unit testing easier.
85type Database interface {
Emily Markovabf24c9e2023-02-08 20:31:11 -080086 AddToMatch(db.TeamMatch) error
Milo Lin1d59f0c2022-06-22 20:30:58 -070087 AddToShift(db.Shift) error
Emily Markova8cb91312024-02-02 12:30:37 -080088 AddToStats2024(db.Stats2024) error
Emily Markovabf24c9e2023-02-08 20:31:11 -080089 ReturnMatches() ([]db.TeamMatch, error)
Filip Kujawaf882e022022-12-14 13:14:08 -080090 ReturnAllNotes() ([]db.NotesData, error)
91 ReturnAllDriverRankings() ([]db.DriverRankingData, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070092 ReturnAllShifts() ([]db.Shift, error)
Emily Markova8cb91312024-02-02 12:30:37 -080093 ReturnStats2024() ([]db.Stats2024, error)
Emily Markova9c18e9c2024-04-03 20:06:27 -070094 ReturnStats2024ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string, compType string) ([]db.Stats2024, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070095 QueryAllShifts(int) ([]db.Shift, error)
Emily Markovae68b7632023-12-30 14:17:55 -080096 QueryNotes(string) ([]string, error)
Emily Markovafaecfe12023-07-01 12:40:03 -070097 QueryPitImages(string) ([]db.RequestedPitImage, error)
Emily Markova8e39f452023-12-23 12:17:30 -080098 ReturnPitImages() ([]db.PitImage, error)
Filip Kujawaf947cb42022-11-21 10:00:30 -080099 AddNotes(db.NotesData) error
Emily Markovafaecfe12023-07-01 12:40:03 -0700100 AddPitImage(db.PitImage) error
Filip Kujawa210a03b2022-11-24 14:41:11 -0800101 AddDriverRanking(db.DriverRankingData) error
Sabina Leaver9b4eb312023-02-20 19:58:17 -0800102 AddAction(db.Action) error
Emily Markova8cb91312024-02-02 12:30:37 -0800103 DeleteFromStats2024(string, int32, int32, string) error
Filip Kujawac1ded372023-05-27 14:33:43 -0700104 DeleteFromActions(string, int32, int32, string) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800105}
106
Emily Markova521725a2024-03-21 18:46:04 -0700107type Clock interface {
108 Now() time.Time
109}
110
111type RealClock struct{}
112
113func (RealClock) Now() time.Time {
114 return time.Now()
115}
116
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800117// Handles unknown requests. Just returns a 404.
118func unknown(w http.ResponseWriter, req *http.Request) {
119 w.WriteHeader(http.StatusNotFound)
120}
121
122func respondWithError(w http.ResponseWriter, statusCode int, errorMessage string) {
123 builder := flatbuffers.NewBuilder(1024)
124 builder.Finish((&error_response.ErrorResponseT{
125 ErrorMessage: errorMessage,
126 }).Pack(builder))
127 w.WriteHeader(statusCode)
128 w.Write(builder.FinishedBytes())
129}
130
131func respondNotImplemented(w http.ResponseWriter) {
132 respondWithError(w, http.StatusNotImplemented, "")
133}
134
Philipp Schraderb7e75932022-03-26 16:18:34 -0700135func 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 -0800136 success := true
137 defer func() {
138 if r := recover(); r != nil {
Philipp Schraderb7e75932022-03-26 16:18:34 -0700139 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse %s: %v", requestName, r))
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800140 success = false
141 }
142 }()
Philipp Schraderb7e75932022-03-26 16:18:34 -0700143 result := parser(buf, 0)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800144 return result, success
145}
146
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700147// Parses the authorization information that the browser inserts into the
148// headers. The authorization follows this format:
149//
Philipp Schrader35bb1532023-03-05 13:49:12 -0800150// req.Headers["Authorization"] = []string{"Basic <base64 encoded username:password>"}
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700151func parseUsername(req *http.Request) string {
152 auth, ok := req.Header["Authorization"]
153 if !ok {
154 return "unknown"
155 }
156
157 parts := strings.Split(auth[0], " ")
158 if !(len(parts) == 2 && parts[0] == "Basic") {
159 return "unknown"
160 }
161
162 info, err := base64.StdEncoding.DecodeString(parts[1])
163 if err != nil {
164 log.Println("ERROR: Failed to parse Basic authentication.")
165 return "unknown"
166 }
167
168 loginParts := strings.Split(string(info), ":")
169 if len(loginParts) != 2 {
170 return "unknown"
171 }
172 return loginParts[0]
173}
174
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800175// Handles a RequestAllMaches request.
176type requestAllMatchesHandler struct {
177 db Database
178}
179
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800180// Change structure of match objects in the database(1 per team) to
181// the old match structure(1 per match) that the webserver uses.
182// We use the information in this struct to identify which match object
183// corresponds to which old match structure object.
184type MatchAssemblyKey struct {
185 MatchNumber int32
186 SetNumber int32
187 CompLevel string
188}
189
Emily Markovabf24c9e2023-02-08 20:31:11 -0800190func findIndexInList(list []string, comp_level string) (int, error) {
191 for index, value := range list {
192 if value == comp_level {
193 return index, nil
194 }
195 }
196 return -1, errors.New(fmt.Sprint("Failed to find comp level ", comp_level, " in list ", list))
197}
198
Emily Markovab8551572023-03-22 19:49:39 -0700199func (handler requestAllMatchesHandler) teamHasBeenDataScouted(key MatchAssemblyKey, teamNumber string) (bool, error) {
Emily Markovadcadcb62024-02-03 13:07:17 -0800200 stats, err := handler.db.ReturnStats2024ForTeam(
Emily Markova9c18e9c2024-04-03 20:06:27 -0700201 teamNumber, key.MatchNumber, key.SetNumber, key.CompLevel, "Regular")
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800202 if err != nil {
203 return false, err
204 }
205 return (len(stats) > 0), nil
206}
207
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800208func (handler requestAllMatchesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
209 requestBytes, err := io.ReadAll(req.Body)
210 if err != nil {
211 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
212 return
213 }
214
Philipp Schraderb7e75932022-03-26 16:18:34 -0700215 _, success := parseRequest(w, requestBytes, "RequestAllMatches", request_all_matches.GetRootAsRequestAllMatches)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800216 if !success {
217 return
218 }
219
220 matches, err := handler.db.ReturnMatches()
221 if err != nil {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700222 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800223 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800224 }
225
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800226 assembledMatches := map[MatchAssemblyKey]request_all_matches_response.MatchT{}
Emily Markovabf24c9e2023-02-08 20:31:11 -0800227
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800228 for _, match := range matches {
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800229 key := MatchAssemblyKey{match.MatchNumber, match.SetNumber, match.CompLevel}
230
231 // Retrieve the converted match structure we have assembled so
232 // far. If we haven't started assembling one yet, then start a
233 // new one.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800234 entry, ok := assembledMatches[key]
235 if !ok {
236 entry = request_all_matches_response.MatchT{
237 MatchNumber: match.MatchNumber,
238 SetNumber: match.SetNumber,
239 CompLevel: match.CompLevel,
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800240 DataScouted: &request_all_matches_response.ScoutedLevelT{},
Emily Markovabf24c9e2023-02-08 20:31:11 -0800241 }
242 }
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800243
Emily Markovab8551572023-03-22 19:49:39 -0700244 var team *string
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800245 var dataScoutedTeam *bool
246
247 // Fill in the field for the match that we have in in the
248 // database. In the database, each match row only has 1 team
249 // number.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800250 switch match.Alliance {
251 case "R":
252 switch match.AlliancePosition {
253 case 1:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800254 team = &entry.R1
255 dataScoutedTeam = &entry.DataScouted.R1
Emily Markovabf24c9e2023-02-08 20:31:11 -0800256 case 2:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800257 team = &entry.R2
258 dataScoutedTeam = &entry.DataScouted.R2
Emily Markovabf24c9e2023-02-08 20:31:11 -0800259 case 3:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800260 team = &entry.R3
261 dataScoutedTeam = &entry.DataScouted.R3
Emily Markovabf24c9e2023-02-08 20:31:11 -0800262 default:
263 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown red position ", strconv.Itoa(int(match.AlliancePosition)), " in match ", strconv.Itoa(int(match.MatchNumber))))
264 return
265 }
266 case "B":
267 switch match.AlliancePosition {
268 case 1:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800269 team = &entry.B1
270 dataScoutedTeam = &entry.DataScouted.B1
Emily Markovabf24c9e2023-02-08 20:31:11 -0800271 case 2:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800272 team = &entry.B2
273 dataScoutedTeam = &entry.DataScouted.B2
Emily Markovabf24c9e2023-02-08 20:31:11 -0800274 case 3:
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800275 team = &entry.B3
276 dataScoutedTeam = &entry.DataScouted.B3
Emily Markovabf24c9e2023-02-08 20:31:11 -0800277 default:
278 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown blue position ", strconv.Itoa(int(match.AlliancePosition)), " in match ", strconv.Itoa(int(match.MatchNumber))))
279 return
280 }
281 default:
282 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown alliance ", match.Alliance, " in match ", strconv.Itoa(int(match.AlliancePosition))))
283 return
284 }
Philipp Schrader0f7b6362023-03-11 14:02:48 -0800285
286 *team = match.TeamNumber
287
288 // Figure out if this team has been data scouted already.
289 *dataScoutedTeam, err = handler.teamHasBeenDataScouted(key, match.TeamNumber)
290 if err != nil {
291 respondWithError(w, http.StatusInternalServerError, fmt.Sprint(
292 "Failed to determine data scouting status for team ",
293 strconv.Itoa(int(match.AlliancePosition)),
294 " in match ",
295 strconv.Itoa(int(match.MatchNumber)),
296 err))
297 return
298 }
299
Emily Markovabf24c9e2023-02-08 20:31:11 -0800300 assembledMatches[key] = entry
301 }
302
303 var response RequestAllMatchesResponseT
304 for _, match := range assembledMatches {
305 copied_match := match
306 response.MatchList = append(response.MatchList, &copied_match)
307 }
308
309 var MATCH_TYPE_ORDERING = []string{"qm", "ef", "qf", "sf", "f"}
310
311 err = nil
312 sort.Slice(response.MatchList, func(i, j int) bool {
313 if err != nil {
314 return false
315 }
316 a := response.MatchList[i]
317 b := response.MatchList[j]
318
Emily Markovaabcac6e2023-02-18 17:50:03 -0800319 aMatchTypeIndex, err2 := findIndexInList(MATCH_TYPE_ORDERING, a.CompLevel)
320 if err2 != nil {
321 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 -0800322 return false
323 }
Emily Markovaabcac6e2023-02-18 17:50:03 -0800324 bMatchTypeIndex, err2 := findIndexInList(MATCH_TYPE_ORDERING, b.CompLevel)
325 if err2 != nil {
326 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 -0800327 return false
328 }
329
330 if aMatchTypeIndex < bMatchTypeIndex {
331 return true
332 }
333 if aMatchTypeIndex > bMatchTypeIndex {
334 return false
335 }
336
337 // Then sort by match number. E.g. in semi finals, all match 1 rounds
338 // are done first. Then come match 2 rounds. And then, if necessary,
339 // the match 3 rounds.
340 aMatchNumber := a.MatchNumber
341 bMatchNumber := b.MatchNumber
342 if aMatchNumber < bMatchNumber {
343 return true
344 }
345 if aMatchNumber > bMatchNumber {
346 return false
347 }
348 // Lastly, sort by set number. I.e. Semi Final 1 Match 1 happens first.
349 // Then comes Semi Final 2 Match 1. Then comes Semi Final 1 Match 2. Then
350 // Semi Final 2 Match 2.
351 aSetNumber := a.SetNumber
352 bSetNumber := b.SetNumber
353 if aSetNumber < bSetNumber {
354 return true
355 }
356 if aSetNumber > bSetNumber {
357 return false
358 }
359 return true
360 })
361
362 if err != nil {
363 // check if error happened during sorting and notify webpage if that
364 respondWithError(w, http.StatusInternalServerError, fmt.Sprint(err))
365 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800366 }
367
368 builder := flatbuffers.NewBuilder(50 * 1024)
369 builder.Finish((&response).Pack(builder))
370 w.Write(builder.FinishedBytes())
371}
372
Emily Markova521725a2024-03-21 18:46:04 -0700373type requestCurrentScoutingHandler struct {
374 // Map that has a key of team number with a value is a map of names to timestamps
375 // so there aren't duplicate timestamps for one person.
376 scoutingMap map[string]map[string]time.Time
377 db Database
378 clock Clock
379}
380
381func (handler requestCurrentScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
382 requestBytes, err := io.ReadAll(req.Body)
383 if err != nil {
384 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
385 return
386 }
387
388 request, success := parseRequest(w, requestBytes, "RequestCurrentScouting", request_current_scouting.GetRootAsRequestCurrentScouting)
389 if !success {
390 return
391 }
392 currentTime := handler.clock.Now()
393 teamNumber := string(request.TeamNumber())
394 collectedBy := parseUsername(req)
395
396 if handler.scoutingMap[teamNumber] == nil {
397 handler.scoutingMap[teamNumber] = map[string]time.Time{}
398 }
399 handler.scoutingMap[teamNumber][collectedBy] = currentTime
400 // Delete any scout information from 10+ seconds ago.
401 for team, teamMap := range handler.scoutingMap {
402 for name, timeStamp := range teamMap {
403 if currentTime.Sub(timeStamp) >= 10*time.Second {
404 delete(handler.scoutingMap[team], name)
405 }
406 }
407 }
408
409 var response RequestCurrentScoutingResponseT
410 for name, _ := range handler.scoutingMap[teamNumber] {
411 if name != collectedBy {
412 response.CollectedBy = append(response.CollectedBy, &request_current_scouting_response.CollectedByT{
413 Name: name,
414 })
415 }
416 }
417
418 builder := flatbuffers.NewBuilder(10)
419 builder.Finish((&response).Pack(builder))
420 w.Write(builder.FinishedBytes())
421}
422
Alex Perry81f96ba2022-03-13 18:26:19 -0700423type submitNoteScoutingHandler struct {
424 db Database
425}
426
427func (handler submitNoteScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
428 requestBytes, err := io.ReadAll(req.Body)
429 if err != nil {
430 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
431 return
432 }
433
Philipp Schraderb7e75932022-03-26 16:18:34 -0700434 request, success := parseRequest(w, requestBytes, "SubmitNotes", submit_notes.GetRootAsSubmitNotes)
Alex Perry81f96ba2022-03-13 18:26:19 -0700435 if !success {
436 return
437 }
438
Filip Kujawaf947cb42022-11-21 10:00:30 -0800439 err = handler.db.AddNotes(db.NotesData{
Emily Markovae68b7632023-12-30 14:17:55 -0800440 TeamNumber: string(request.Team()),
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800441 Notes: string(request.Notes()),
442 GoodDriving: bool(request.GoodDriving()),
443 BadDriving: bool(request.BadDriving()),
Filip Kujawa11dc4c92023-04-13 08:55:43 -0700444 SolidPlacing: bool(request.SolidPlacing()),
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800445 SketchyPlacing: bool(request.SketchyPlacing()),
446 GoodDefense: bool(request.GoodDefense()),
447 BadDefense: bool(request.BadDefense()),
448 EasilyDefended: bool(request.EasilyDefended()),
Emily Markovacf893f42024-03-13 19:03:10 -0700449 NoShow: bool(request.NoShow()),
450 MatchNumber: request.MatchNumber(),
451 SetNumber: request.SetNumber(),
452 CompLevel: string(request.CompLevel()),
Filip Kujawaf947cb42022-11-21 10:00:30 -0800453 })
Alex Perry81f96ba2022-03-13 18:26:19 -0700454 if err != nil {
455 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert notes: %v", err))
456 return
457 }
458
459 var response SubmitNotesResponseT
460 builder := flatbuffers.NewBuilder(10)
461 builder.Finish((&response).Pack(builder))
462 w.Write(builder.FinishedBytes())
463}
464
Emily Markovafaecfe12023-07-01 12:40:03 -0700465type submitPitImageScoutingHandler struct {
466 db Database
467}
468
469func (handler submitPitImageScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
470 requestBytes, err := io.ReadAll(req.Body)
471 if err != nil {
472 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
473 return
474 }
475
476 request, success := parseRequest(w, requestBytes, "SubmitPitImage", submit_pit_image.GetRootAsSubmitPitImage)
477 if !success {
478 return
479 }
480
481 err = handler.db.AddPitImage(db.PitImage{
482 TeamNumber: string(request.TeamNumber()),
483 CheckSum: db.ComputeSha256FromByteArray(request.ImageDataBytes()),
484 ImagePath: string(request.ImagePath()),
485 ImageData: request.ImageDataBytes(),
486 })
487 if err != nil {
488 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert notes: %v", err))
489 return
490 }
491
492 var response SubmitPitImageResponseT
493 builder := flatbuffers.NewBuilder(10)
494 builder.Finish((&response).Pack(builder))
495 w.Write(builder.FinishedBytes())
496}
497
Emily Markova8cb91312024-02-02 12:30:37 -0800498func ConvertActionsToStat2024(submit2024Actions *submit_2024_actions.Submit2024Actions) (db.Stats2024, error) {
499 overall_time := int64(0)
500 cycles := int64(0)
501 picked_up := false
502 lastPlacedTime := int64(0)
503 stat := db.Stats2024{
Emily Markova9c18e9c2024-04-03 20:06:27 -0700504 CompType: string(submit2024Actions.CompType()), TeamNumber: string(submit2024Actions.TeamNumber()),
505 MatchNumber: submit2024Actions.MatchNumber(), SetNumber: submit2024Actions.SetNumber(), CompLevel: string(submit2024Actions.CompLevel()),
Emily Markova8cb91312024-02-02 12:30:37 -0800506 StartingQuadrant: 0, SpeakerAuto: 0, AmpAuto: 0, NotesDroppedAuto: 0, MobilityAuto: false,
Emily Markovacd156942024-04-07 19:32:28 -0700507 Speaker: 0, Amp: 0, SpeakerAmplified: 0, NotesDropped: 0, Shuttled: 0, OutOfField: 0, Penalties: 0,
Emily Markovaf17f2812024-04-03 20:55:12 -0700508 TrapNote: false, Spotlight: false, AvgCycle: 0, Park: false, OnStage: false, Harmony: false, RobotDied: false, NoShow: false, CollectedBy: "",
Emily Markova8cb91312024-02-02 12:30:37 -0800509 }
510 // Loop over all actions.
511 for i := 0; i < submit2024Actions.ActionsListLength(); i++ {
512 var action submit_2024_actions.Action
513 if !submit2024Actions.ActionsList(&action, i) {
514 return db.Stats2024{}, errors.New(fmt.Sprintf("Failed to parse submit_2024_actions.Action"))
515 }
516 actionTable := new(flatbuffers.Table)
517 action_type := action.ActionTakenType()
518 if !action.ActionTaken(actionTable) {
519 return db.Stats2024{}, errors.New(fmt.Sprint("Failed to parse sub-action or sub-action was missing"))
520 }
521 if action_type == submit_2024_actions.ActionTypeStartMatchAction {
522 var startMatchAction submit_2024_actions.StartMatchAction
523 startMatchAction.Init(actionTable.Bytes, actionTable.Pos)
524 stat.StartingQuadrant = startMatchAction.Position()
525 } else if action_type == submit_2024_actions.ActionTypeMobilityAction {
526 var mobilityAction submit_2024_actions.MobilityAction
527 mobilityAction.Init(actionTable.Bytes, actionTable.Pos)
528 if mobilityAction.Mobility() {
529 stat.MobilityAuto = true
530 }
Emily Markova8cb91312024-02-02 12:30:37 -0800531 } else if action_type == submit_2024_actions.ActionTypePenaltyAction {
532 var penaltyAction submit_2024_actions.PenaltyAction
533 penaltyAction.Init(actionTable.Bytes, actionTable.Pos)
Emily Markovadcadcb62024-02-03 13:07:17 -0800534 stat.Penalties += penaltyAction.Penalties()
Emily Markova8cb91312024-02-02 12:30:37 -0800535
Emily Markova040123c2024-02-27 09:48:37 -0800536 } else if action_type == submit_2024_actions.ActionTypeRobotDeathAction {
537 var robotDeathAction submit_2024_actions.RobotDeathAction
538 robotDeathAction.Init(actionTable.Bytes, actionTable.Pos)
539 stat.RobotDied = true
540
Emily Markovaf17f2812024-04-03 20:55:12 -0700541 } else if action_type == submit_2024_actions.ActionTypeNoShowAction {
542 var NoShowAction submit_2024_actions.NoShowAction
543 NoShowAction.Init(actionTable.Bytes, actionTable.Pos)
544 stat.NoShow = true
545
Emily Markova8cb91312024-02-02 12:30:37 -0800546 } else if action_type == submit_2024_actions.ActionTypePickupNoteAction {
547 var pick_up_action submit_2024_actions.PickupNoteAction
548 pick_up_action.Init(actionTable.Bytes, actionTable.Pos)
Emily Markova040123c2024-02-27 09:48:37 -0800549 picked_up = true
Emily Markova8cb91312024-02-02 12:30:37 -0800550 } else if action_type == submit_2024_actions.ActionTypePlaceNoteAction {
551 var place_action submit_2024_actions.PlaceNoteAction
552 place_action.Init(actionTable.Bytes, actionTable.Pos)
553 if !picked_up {
554 return db.Stats2024{}, errors.New(fmt.Sprintf("Got PlaceNoteAction without corresponding PickupObjectAction"))
555 }
556 score_type := place_action.ScoreType()
557 auto := place_action.Auto()
Emily Markovacd156942024-04-07 19:32:28 -0700558 count_in_cycle := true
Emily Markova8cb91312024-02-02 12:30:37 -0800559 if score_type == submit_2024_actions.ScoreTypekAMP && auto {
560 stat.AmpAuto += 1
561 } else if score_type == submit_2024_actions.ScoreTypekAMP && !auto {
562 stat.Amp += 1
Emily Markova8cb91312024-02-02 12:30:37 -0800563 } else if score_type == submit_2024_actions.ScoreTypekSPEAKER && !auto {
564 stat.Speaker += 1
565 } else if score_type == submit_2024_actions.ScoreTypekSPEAKER && auto {
566 stat.SpeakerAuto += 1
567 } else if score_type == submit_2024_actions.ScoreTypekSPEAKER_AMPLIFIED && !auto {
568 stat.SpeakerAmplified += 1
Emily Markova040123c2024-02-27 09:48:37 -0800569 } else if score_type == submit_2024_actions.ScoreTypekDROPPED && auto {
570 stat.NotesDroppedAuto += 1
Emily Markovacd156942024-04-07 19:32:28 -0700571 count_in_cycle = false
Emily Markova040123c2024-02-27 09:48:37 -0800572 } else if score_type == submit_2024_actions.ScoreTypekDROPPED && !auto {
573 stat.NotesDropped += 1
Emily Markovacd156942024-04-07 19:32:28 -0700574 count_in_cycle = false
575 } else if score_type == submit_2024_actions.ScoreTypekSHUTTLED {
576 stat.Shuttled += 1
577 count_in_cycle = false
578 } else if score_type == submit_2024_actions.ScoreTypekOUT_OF_FIELD {
579 stat.OutOfField += 1
580 count_in_cycle = false
Emily Markova8cb91312024-02-02 12:30:37 -0800581 } else {
582 return db.Stats2024{}, errors.New(fmt.Sprintf("Got unknown ObjectType/ScoreLevel/Auto combination"))
583 }
584 picked_up = false
Emily Markovacd156942024-04-07 19:32:28 -0700585 if count_in_cycle {
586 // Assuming dropped, shuttled, and out of field
587 // notes are not counted in total cycle time.
588 if lastPlacedTime != int64(0) {
589 // If this is not the first time we place,
590 // start counting cycle time. We define cycle
591 // time as the time between placements.
592 overall_time += int64(action.Timestamp()) - lastPlacedTime
593 }
Emily Markova8cb91312024-02-02 12:30:37 -0800594 cycles += 1
Emily Markovacd156942024-04-07 19:32:28 -0700595 lastPlacedTime = int64(action.Timestamp())
Emily Markova8cb91312024-02-02 12:30:37 -0800596 }
Emily Markova8cb91312024-02-02 12:30:37 -0800597 } else if action_type == submit_2024_actions.ActionTypeEndMatchAction {
598 var endMatchAction submit_2024_actions.EndMatchAction
599 endMatchAction.Init(actionTable.Bytes, actionTable.Pos)
600 if endMatchAction.StageType() == submit_2024_actions.StageTypekON_STAGE {
601 stat.OnStage = true
602 } else if endMatchAction.StageType() == submit_2024_actions.StageTypekPARK {
603 stat.Park = true
604 } else if endMatchAction.StageType() == submit_2024_actions.StageTypekHARMONY {
605 stat.Harmony = true
606 }
607 stat.TrapNote = endMatchAction.TrapNote()
Emily Markova6079e2f2024-02-17 13:17:24 -0800608 stat.Spotlight = endMatchAction.Spotlight()
Emily Markova8cb91312024-02-02 12:30:37 -0800609 }
610 }
611 if cycles != 0 {
612 stat.AvgCycle = overall_time / cycles
613 } else {
614 stat.AvgCycle = 0
615 }
616 return stat, nil
617}
618
619// Handles a Request2024DataScouting request.
620type request2024DataScoutingHandler struct {
621 db Database
622}
623
624func (handler request2024DataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
625 requestBytes, err := io.ReadAll(req.Body)
626 if err != nil {
627 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
628 return
629 }
630
631 _, success := parseRequest(w, requestBytes, "Request2024DataScouting", request_2024_data_scouting.GetRootAsRequest2024DataScouting)
632 if !success {
633 return
634 }
635
636 stats, err := handler.db.ReturnStats2024()
637 if err != nil {
638 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
639 return
640 }
641
642 var response Request2024DataScoutingResponseT
643 for _, stat := range stats {
644 response.StatsList = append(response.StatsList, &request_2024_data_scouting_response.Stats2024T{
645 TeamNumber: stat.TeamNumber,
646 MatchNumber: stat.MatchNumber,
647 SetNumber: stat.SetNumber,
648 CompLevel: stat.CompLevel,
649 StartingQuadrant: stat.StartingQuadrant,
650 SpeakerAuto: stat.SpeakerAuto,
651 AmpAuto: stat.AmpAuto,
652 NotesDroppedAuto: stat.NotesDroppedAuto,
653 MobilityAuto: stat.MobilityAuto,
654 Speaker: stat.Speaker,
655 Amp: stat.Amp,
656 SpeakerAmplified: stat.SpeakerAmplified,
Emily Markova8cb91312024-02-02 12:30:37 -0800657 NotesDropped: stat.NotesDropped,
Emily Markovacd156942024-04-07 19:32:28 -0700658 Shuttled: stat.Shuttled,
659 OutOfField: stat.OutOfField,
Emily Markova8cb91312024-02-02 12:30:37 -0800660 Penalties: stat.Penalties,
661 TrapNote: stat.TrapNote,
Emily Markova6079e2f2024-02-17 13:17:24 -0800662 Spotlight: stat.Spotlight,
Emily Markova8cb91312024-02-02 12:30:37 -0800663 AvgCycle: stat.AvgCycle,
664 Park: stat.Park,
665 OnStage: stat.OnStage,
666 Harmony: stat.Harmony,
Emily Markova040123c2024-02-27 09:48:37 -0800667 RobotDied: stat.RobotDied,
Emily Markovaf17f2812024-04-03 20:55:12 -0700668 NoShow: stat.NoShow,
Emily Markova8cb91312024-02-02 12:30:37 -0800669 CollectedBy: stat.CollectedBy,
Emily Markova9c18e9c2024-04-03 20:06:27 -0700670 CompType: stat.CompType,
Emily Markova8cb91312024-02-02 12:30:37 -0800671 })
672 }
673
674 builder := flatbuffers.NewBuilder(50 * 1024)
675 builder.Finish((&response).Pack(builder))
676 w.Write(builder.FinishedBytes())
677}
678
Emily Markova8e39f452023-12-23 12:17:30 -0800679type requestAllPitImagesHandler struct {
680 db Database
681}
682
683func (handler requestAllPitImagesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
684 requestBytes, err := io.ReadAll(req.Body)
685 if err != nil {
686 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
687 return
688 }
689
690 _, success := parseRequest(w, requestBytes, "RequestAllPitImages", request_all_pit_images.GetRootAsRequestAllPitImages)
691 if !success {
692 return
693 }
694
695 images, err := handler.db.ReturnPitImages()
696 if err != nil {
697 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get pit images: %v", err))
698 return
699 }
700
701 var response RequestAllPitImagesResponseT
702 for _, data := range images {
703 response.PitImageList = append(response.PitImageList, &request_all_pit_images_response.PitImageT{
704 TeamNumber: data.TeamNumber,
705 ImagePath: data.ImagePath,
706 CheckSum: data.CheckSum,
707 })
708 }
709
710 builder := flatbuffers.NewBuilder(1024)
711 builder.Finish((&response).Pack(builder))
712 w.Write(builder.FinishedBytes())
713}
714
Emily Markovafaecfe12023-07-01 12:40:03 -0700715type requestPitImagesHandler struct {
716 db Database
717}
718
719func (handler requestPitImagesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
720 requestBytes, err := io.ReadAll(req.Body)
721 if err != nil {
722 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
723 return
724 }
725
726 request, success := parseRequest(w, requestBytes, "RequestPitImages", request_pit_images.GetRootAsRequestPitImages)
727 if !success {
728 return
729 }
730
731 images, err := handler.db.QueryPitImages(string(request.TeamNumber()))
732 if err != nil {
733 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query pit images: %v", err))
734 return
735 }
736
737 var response RequestPitImagesResponseT
738 for _, data := range images {
739 response.PitImageList = append(response.PitImageList, &request_pit_images_response.PitImageT{
740 TeamNumber: data.TeamNumber,
741 ImagePath: data.ImagePath,
742 CheckSum: data.CheckSum,
743 })
744 }
745
746 builder := flatbuffers.NewBuilder(1024)
747 builder.Finish((&response).Pack(builder))
748 w.Write(builder.FinishedBytes())
749}
750
Alex Perry81f96ba2022-03-13 18:26:19 -0700751type requestNotesForTeamHandler struct {
752 db Database
753}
754
755func (handler requestNotesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
756 requestBytes, err := io.ReadAll(req.Body)
757 if err != nil {
758 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
759 return
760 }
761
Philipp Schraderb7e75932022-03-26 16:18:34 -0700762 request, success := parseRequest(w, requestBytes, "RequestNotesForTeam", request_notes_for_team.GetRootAsRequestNotesForTeam)
Alex Perry81f96ba2022-03-13 18:26:19 -0700763 if !success {
764 return
765 }
766
Emily Markovae68b7632023-12-30 14:17:55 -0800767 notes, err := handler.db.QueryNotes(string(request.Team()))
Alex Perry81f96ba2022-03-13 18:26:19 -0700768 if err != nil {
769 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query notes: %v", err))
770 return
771 }
772
773 var response RequestNotesForTeamResponseT
Philipp Schradereecb8962022-06-01 21:02:42 -0700774 for _, data := range notes {
Alex Perry81f96ba2022-03-13 18:26:19 -0700775 response.Notes = append(response.Notes, &request_notes_for_team_response.NoteT{data})
776 }
777
778 builder := flatbuffers.NewBuilder(1024)
779 builder.Finish((&response).Pack(builder))
780 w.Write(builder.FinishedBytes())
781}
782
Milo Lin1d59f0c2022-06-22 20:30:58 -0700783type requestShiftScheduleHandler struct {
784 db Database
785}
786
787func (handler requestShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
788 requestBytes, err := io.ReadAll(req.Body)
789 if err != nil {
790 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
791 return
792 }
793
794 _, success := parseRequest(w, requestBytes, "RequestShiftSchedule", request_shift_schedule.GetRootAsRequestShiftSchedule)
795 if !success {
796 return
797 }
798
799 shiftData, err := handler.db.ReturnAllShifts()
800 if err != nil {
801 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query shift schedule: %v", err))
802 return
803 }
804
805 var response RequestShiftScheduleResponseT
806 for _, shifts := range shiftData {
807 response.ShiftSchedule = append(response.ShiftSchedule, &request_shift_schedule_response.MatchAssignmentT{
808 MatchNumber: shifts.MatchNumber,
Philipp Schrader2ff455b2023-05-03 22:11:50 -0700809 R1Scouter: shifts.R1scouter,
810 R2Scouter: shifts.R2scouter,
811 R3Scouter: shifts.R3scouter,
812 B1Scouter: shifts.B1scouter,
813 B2Scouter: shifts.B2scouter,
814 B3Scouter: shifts.B3scouter,
Milo Lin1d59f0c2022-06-22 20:30:58 -0700815 })
816 }
817
818 builder := flatbuffers.NewBuilder(1024)
819 builder.Finish((&response).Pack(builder))
820 w.Write(builder.FinishedBytes())
821}
822
823type submitShiftScheduleHandler struct {
824 db Database
825}
826
827func (handler submitShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
828 // Get the username of the person submitting the data.
829 username := parseUsername(req)
830
831 requestBytes, err := io.ReadAll(req.Body)
832 if err != nil {
833 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
834 return
835 }
836
837 request, success := parseRequest[SubmitShiftSchedule](w, requestBytes, "SubmitShiftSchedule", submit_shift_schedule.GetRootAsSubmitShiftSchedule)
838 if !success {
839 return
840 }
841
842 log.Println("Got shift schedule from", username)
843 shift_schedule_length := request.ShiftScheduleLength()
844 for i := 0; i < shift_schedule_length; i++ {
845 var match_assignment submit_shift_schedule.MatchAssignment
846 request.ShiftSchedule(&match_assignment, i)
847 current_shift := db.Shift{
848 MatchNumber: match_assignment.MatchNumber(),
Philipp Schrader2ff455b2023-05-03 22:11:50 -0700849 R1scouter: string(match_assignment.R1Scouter()),
850 R2scouter: string(match_assignment.R2Scouter()),
851 R3scouter: string(match_assignment.R3Scouter()),
852 B1scouter: string(match_assignment.B1Scouter()),
853 B2scouter: string(match_assignment.B2Scouter()),
854 B3scouter: string(match_assignment.B3Scouter()),
Milo Lin1d59f0c2022-06-22 20:30:58 -0700855 }
856 err = handler.db.AddToShift(current_shift)
857 if err != nil {
858 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit shift schedule: ", err))
859 return
860 }
861 }
862
863 builder := flatbuffers.NewBuilder(50 * 1024)
864 builder.Finish((&SubmitShiftScheduleResponseT{}).Pack(builder))
865 w.Write(builder.FinishedBytes())
866}
867
Filip Kujawa210a03b2022-11-24 14:41:11 -0800868type SubmitDriverRankingHandler struct {
869 db Database
870}
871
872func (handler SubmitDriverRankingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
873 requestBytes, err := io.ReadAll(req.Body)
874 if err != nil {
875 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
876 return
877 }
878
879 request, success := parseRequest(w, requestBytes, "SubmitDriverRanking", submit_driver_ranking.GetRootAsSubmitDriverRanking)
880 if !success {
881 return
882 }
883
884 err = handler.db.AddDriverRanking(db.DriverRankingData{
885 MatchNumber: request.MatchNumber(),
Emily Markovae68b7632023-12-30 14:17:55 -0800886 Rank1: string(request.Rank1()),
887 Rank2: string(request.Rank2()),
888 Rank3: string(request.Rank3()),
Filip Kujawa210a03b2022-11-24 14:41:11 -0800889 })
890
891 if err != nil {
892 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert driver ranking: %v", err))
893 return
894 }
895
896 var response SubmitDriverRankingResponseT
897 builder := flatbuffers.NewBuilder(10)
898 builder.Finish((&response).Pack(builder))
899 w.Write(builder.FinishedBytes())
900}
901
Filip Kujawaf882e022022-12-14 13:14:08 -0800902type requestAllNotesHandler struct {
903 db Database
904}
905
906func (handler requestAllNotesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
907 requestBytes, err := io.ReadAll(req.Body)
908 if err != nil {
909 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
910 return
911 }
912
913 _, success := parseRequest(w, requestBytes, "RequestAllNotes", request_all_notes.GetRootAsRequestAllNotes)
914 if !success {
915 return
916 }
917
918 notes, err := handler.db.ReturnAllNotes()
919 if err != nil {
920 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
921 return
922 }
923
924 var response RequestAllNotesResponseT
925 for _, note := range notes {
926 response.NoteList = append(response.NoteList, &request_all_notes_response.NoteT{
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800927 Team: note.TeamNumber,
928 Notes: note.Notes,
929 GoodDriving: note.GoodDriving,
930 BadDriving: note.BadDriving,
Filip Kujawa11dc4c92023-04-13 08:55:43 -0700931 SolidPlacing: note.SolidPlacing,
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800932 SketchyPlacing: note.SketchyPlacing,
933 GoodDefense: note.GoodDefense,
934 BadDefense: note.BadDefense,
935 EasilyDefended: note.EasilyDefended,
Emily Markovacf893f42024-03-13 19:03:10 -0700936 NoShow: note.NoShow,
937 MatchNumber: note.MatchNumber,
938 CompLevel: note.CompLevel,
939 SetNumber: note.SetNumber,
Filip Kujawaf882e022022-12-14 13:14:08 -0800940 })
941 }
942
943 builder := flatbuffers.NewBuilder(50 * 1024)
944 builder.Finish((&response).Pack(builder))
945 w.Write(builder.FinishedBytes())
946}
947
948type requestAllDriverRankingsHandler struct {
949 db Database
950}
951
952func (handler requestAllDriverRankingsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
953 requestBytes, err := io.ReadAll(req.Body)
954 if err != nil {
955 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
956 return
957 }
958
959 _, success := parseRequest(w, requestBytes, "RequestAllDriverRankings", request_all_driver_rankings.GetRootAsRequestAllDriverRankings)
960 if !success {
961 return
962 }
963
964 rankings, err := handler.db.ReturnAllDriverRankings()
965 if err != nil {
966 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
967 return
968 }
969
970 var response RequestAllDriverRankingsResponseT
971 for _, ranking := range rankings {
972 response.DriverRankingList = append(response.DriverRankingList, &request_all_driver_rankings_response.RankingT{
973 MatchNumber: ranking.MatchNumber,
974 Rank1: ranking.Rank1,
975 Rank2: ranking.Rank2,
976 Rank3: ranking.Rank3,
977 })
978 }
979
980 builder := flatbuffers.NewBuilder(50 * 1024)
981 builder.Finish((&response).Pack(builder))
982 w.Write(builder.FinishedBytes())
983}
984
Emily Markova8cb91312024-02-02 12:30:37 -0800985type submit2024ActionsHandler struct {
986 db Database
987}
988
989func (handler submit2024ActionsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
990 // Get the username of the person submitting the data.
991 username := parseUsername(req)
992
993 requestBytes, err := io.ReadAll(req.Body)
994 if err != nil {
Philipp Schradere9719242024-04-18 09:30:34 -0700995 log.Println("Failed to receive submission request from", username)
Emily Markova8cb91312024-02-02 12:30:37 -0800996 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
997 return
998 }
999
1000 request, success := parseRequest(w, requestBytes, "Submit2024Actions", submit_2024_actions.GetRootAsSubmit2024Actions)
1001 if !success {
Philipp Schradere9719242024-04-18 09:30:34 -07001002 log.Println("Failed to parse submission request from", username)
Emily Markova8cb91312024-02-02 12:30:37 -08001003 return
1004 }
1005
Philipp Schradere9719242024-04-18 09:30:34 -07001006 log.Println("Got actions for match", request.MatchNumber(), "team", string(request.TeamNumber()), "type", string(request.CompType()), "from", username)
Emily Markova8cb91312024-02-02 12:30:37 -08001007
1008 for i := 0; i < request.ActionsListLength(); i++ {
1009
1010 var action Action2024
1011 request.ActionsList(&action, i)
1012
1013 dbAction := db.Action{
Emily Markova9c18e9c2024-04-03 20:06:27 -07001014 CompType: string(request.CompType()),
Emily Markova8cb91312024-02-02 12:30:37 -08001015 TeamNumber: string(request.TeamNumber()),
1016 MatchNumber: request.MatchNumber(),
1017 SetNumber: request.SetNumber(),
1018 CompLevel: string(request.CompLevel()),
1019 //TODO: Serialize CompletedAction
1020 CompletedAction: []byte{},
1021 Timestamp: action.Timestamp(),
1022 CollectedBy: username,
1023 }
1024
1025 // Do some error checking.
1026 if action.Timestamp() < 0 {
Philipp Schradere9719242024-04-18 09:30:34 -07001027 log.Println("Got action with invalid timestamp (", action.Timestamp(), ") from", username)
Emily Markova8cb91312024-02-02 12:30:37 -08001028 respondWithError(w, http.StatusBadRequest, fmt.Sprint(
1029 "Invalid timestamp field value of ", action.Timestamp()))
1030 return
1031 }
1032
1033 err = handler.db.AddAction(dbAction)
1034 if err != nil {
Philipp Schradere9719242024-04-18 09:30:34 -07001035 log.Println("Failed to add action from", username, "to the database:", err)
Emily Markova8cb91312024-02-02 12:30:37 -08001036 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to add action to database: ", err))
1037 return
1038 }
1039 }
1040
1041 stats, err := ConvertActionsToStat2024(request)
1042 if err != nil {
Philipp Schradere9719242024-04-18 09:30:34 -07001043 log.Println("Failed to add action from", username, "to the database:", err)
Emily Markova8cb91312024-02-02 12:30:37 -08001044 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to convert actions to stats: ", err))
1045 return
1046 }
1047
1048 stats.CollectedBy = username
1049
1050 err = handler.db.AddToStats2024(stats)
1051 if err != nil {
Philipp Schradere9719242024-04-18 09:30:34 -07001052 log.Println("Failed to submit stats from", username, "to the database:", err)
Emily Markovadcadcb62024-02-03 13:07:17 -08001053 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit stats2024: ", stats, ": ", err))
Emily Markova8cb91312024-02-02 12:30:37 -08001054 return
1055 }
1056
1057 builder := flatbuffers.NewBuilder(50 * 1024)
Emily Markovadcadcb62024-02-03 13:07:17 -08001058 builder.Finish((&Submit2024ActionsResponseT{}).Pack(builder))
Emily Markova8cb91312024-02-02 12:30:37 -08001059 w.Write(builder.FinishedBytes())
Philipp Schradere9719242024-04-18 09:30:34 -07001060
1061 log.Println("Successfully added stats from", username)
Emily Markova8cb91312024-02-02 12:30:37 -08001062}
1063
Emily Markova8cb91312024-02-02 12:30:37 -08001064type Delete2024DataScoutingHandler struct {
1065 db Database
1066}
1067
1068func (handler Delete2024DataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
1069 requestBytes, err := io.ReadAll(req.Body)
1070 if err != nil {
1071 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
1072 return
1073 }
1074
1075 request, success := parseRequest(w, requestBytes, "Delete2024DataScouting", delete_2024_data_scouting.GetRootAsDelete2024DataScouting)
1076 if !success {
1077 return
1078 }
1079
1080 err = handler.db.DeleteFromStats2024(
1081 string(request.CompLevel()),
1082 request.MatchNumber(),
1083 request.SetNumber(),
1084 string(request.TeamNumber()))
1085
1086 if err != nil {
1087 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to delete from stats2024: %v", err))
1088 return
1089 }
1090
1091 err = handler.db.DeleteFromActions(
1092 string(request.CompLevel()),
1093 request.MatchNumber(),
1094 request.SetNumber(),
1095 string(request.TeamNumber()))
1096
1097 if err != nil {
1098 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to delete from actions: %v", err))
1099 return
1100 }
1101
1102 var response Delete2024DataScoutingResponseT
1103 builder := flatbuffers.NewBuilder(10)
1104 builder.Finish((&response).Pack(builder))
1105 w.Write(builder.FinishedBytes())
1106}
1107
Emily Markova521725a2024-03-21 18:46:04 -07001108func HandleRequests(db Database, scoutingServer server.ScoutingServer, clock Clock) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08001109 scoutingServer.HandleFunc("/requests", unknown)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -08001110 scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
Filip Kujawaf882e022022-12-14 13:14:08 -08001111 scoutingServer.Handle("/requests/request/all_notes", requestAllNotesHandler{db})
1112 scoutingServer.Handle("/requests/request/all_driver_rankings", requestAllDriverRankingsHandler{db})
Emily Markova8cb91312024-02-02 12:30:37 -08001113 scoutingServer.Handle("/requests/request/2024_data_scouting", request2024DataScoutingHandler{db})
Alex Perry81f96ba2022-03-13 18:26:19 -07001114 scoutingServer.Handle("/requests/submit/submit_notes", submitNoteScoutingHandler{db})
Emily Markovafaecfe12023-07-01 12:40:03 -07001115 scoutingServer.Handle("/requests/submit/submit_pit_image", submitPitImageScoutingHandler{db})
1116 scoutingServer.Handle("/requests/request/pit_images", requestPitImagesHandler{db})
Emily Markova8e39f452023-12-23 12:17:30 -08001117 scoutingServer.Handle("/requests/request/all_pit_images", requestAllPitImagesHandler{db})
Emily Markova521725a2024-03-21 18:46:04 -07001118 scoutingServer.Handle("/requests/request/current_scouting", requestCurrentScoutingHandler{make(map[string]map[string]time.Time), db, clock})
Alex Perry81f96ba2022-03-13 18:26:19 -07001119 scoutingServer.Handle("/requests/request/notes_for_team", requestNotesForTeamHandler{db})
Milo Lin1d59f0c2022-06-22 20:30:58 -07001120 scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
1121 scoutingServer.Handle("/requests/request/shift_schedule", requestShiftScheduleHandler{db})
Filip Kujawa210a03b2022-11-24 14:41:11 -08001122 scoutingServer.Handle("/requests/submit/submit_driver_ranking", SubmitDriverRankingHandler{db})
Emily Markova8cb91312024-02-02 12:30:37 -08001123 scoutingServer.Handle("/requests/submit/submit_2024_actions", submit2024ActionsHandler{db})
Emily Markova8cb91312024-02-02 12:30:37 -08001124 scoutingServer.Handle("/requests/delete/delete_2024_data_scouting", Delete2024DataScoutingHandler{db})
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08001125}