blob: 062398a4de12f77a38edd89101d8da080b1d1233 [file] [log] [blame]
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08001package requests
2
3import (
Philipp Schraderfae8a7e2022-03-13 22:51:54 -07004 "encoding/base64"
Philipp Schraderd3fac192022-03-02 20:35:46 -08005 "errors"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08006 "fmt"
7 "io"
Philipp Schraderfae8a7e2022-03-13 22:51:54 -07008 "log"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08009 "net/http"
Emily Markovabf24c9e2023-02-08 20:31:11 -080010 "sort"
Philipp Schraderd3fac192022-03-02 20:35:46 -080011 "strconv"
12 "strings"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080013
Philipp Schrader8747f1b2022-02-23 23:56:22 -080014 "github.com/frc971/971-Robot-Code/scouting/db"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080015 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
Emily Markova290147d2023-03-03 22:40:06 -080016 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_2023_data_scouting"
17 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_2023_data_scouting_response"
Filip Kujawaf882e022022-12-14 13:14:08 -080018 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_driver_rankings"
19 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_driver_rankings_response"
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080020 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches"
21 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches_response"
Filip Kujawaf882e022022-12-14 13:14:08 -080022 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_notes"
23 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_notes_response"
Philipp Schraderacf96232022-03-01 22:03:30 -080024 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_data_scouting"
25 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_data_scouting_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070026 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team"
27 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070028 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule"
29 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule_response"
Sabina Leaver759090b2023-01-14 20:42:56 -080030 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_actions"
31 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_actions_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080032 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting"
Philipp Schrader30005e42022-03-06 13:53:58 -080033 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
Filip Kujawa210a03b2022-11-24 14:41:11 -080034 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking"
35 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_driver_ranking_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070036 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes"
37 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
Milo Lin1d59f0c2022-06-22 20:30:58 -070038 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule"
39 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080040 "github.com/frc971/971-Robot-Code/scouting/webserver/server"
41 flatbuffers "github.com/google/flatbuffers/go"
42)
43
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080044type SubmitDataScouting = submit_data_scouting.SubmitDataScouting
Philipp Schrader30005e42022-03-06 13:53:58 -080045type SubmitDataScoutingResponseT = submit_data_scouting_response.SubmitDataScoutingResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080046type RequestAllMatches = request_all_matches.RequestAllMatches
47type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
Filip Kujawaf882e022022-12-14 13:14:08 -080048type RequestAllDriverRankings = request_all_driver_rankings.RequestAllDriverRankings
49type RequestAllDriverRankingsResponseT = request_all_driver_rankings_response.RequestAllDriverRankingsResponseT
50type RequestAllNotes = request_all_notes.RequestAllNotes
51type RequestAllNotesResponseT = request_all_notes_response.RequestAllNotesResponseT
Philipp Schraderacf96232022-03-01 22:03:30 -080052type RequestDataScouting = request_data_scouting.RequestDataScouting
53type RequestDataScoutingResponseT = request_data_scouting_response.RequestDataScoutingResponseT
Emily Markova290147d2023-03-03 22:40:06 -080054type Request2023DataScouting = request_2023_data_scouting.Request2023DataScouting
55type Request2023DataScoutingResponseT = request_2023_data_scouting_response.Request2023DataScoutingResponseT
Alex Perry81f96ba2022-03-13 18:26:19 -070056type SubmitNotes = submit_notes.SubmitNotes
57type SubmitNotesResponseT = submit_notes_response.SubmitNotesResponseT
58type RequestNotesForTeam = request_notes_for_team.RequestNotesForTeam
59type RequestNotesForTeamResponseT = request_notes_for_team_response.RequestNotesForTeamResponseT
Milo Lin1d59f0c2022-06-22 20:30:58 -070060type RequestShiftSchedule = request_shift_schedule.RequestShiftSchedule
61type RequestShiftScheduleResponseT = request_shift_schedule_response.RequestShiftScheduleResponseT
62type SubmitShiftSchedule = submit_shift_schedule.SubmitShiftSchedule
63type SubmitShiftScheduleResponseT = submit_shift_schedule_response.SubmitShiftScheduleResponseT
Filip Kujawa210a03b2022-11-24 14:41:11 -080064type SubmitDriverRanking = submit_driver_ranking.SubmitDriverRanking
65type SubmitDriverRankingResponseT = submit_driver_ranking_response.SubmitDriverRankingResponseT
Sabina Leaver759090b2023-01-14 20:42:56 -080066type SubmitActions = submit_actions.SubmitActions
67type SubmitActionsResponseT = submit_actions_response.SubmitActionsResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080068
Philipp Schrader8747f1b2022-02-23 23:56:22 -080069// The interface we expect the database abstraction to conform to.
70// We use an interface here because it makes unit testing easier.
71type Database interface {
Emily Markovabf24c9e2023-02-08 20:31:11 -080072 AddToMatch(db.TeamMatch) error
Milo Lin1d59f0c2022-06-22 20:30:58 -070073 AddToShift(db.Shift) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080074 AddToStats(db.Stats) error
Emily Markova290147d2023-03-03 22:40:06 -080075 AddToStats2023(db.Stats2023) error
Emily Markovabf24c9e2023-02-08 20:31:11 -080076 ReturnMatches() ([]db.TeamMatch, error)
Filip Kujawaf882e022022-12-14 13:14:08 -080077 ReturnAllNotes() ([]db.NotesData, error)
78 ReturnAllDriverRankings() ([]db.DriverRankingData, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070079 ReturnAllShifts() ([]db.Shift, error)
Philipp Schrader8747f1b2022-02-23 23:56:22 -080080 ReturnStats() ([]db.Stats, error)
Emily Markova290147d2023-03-03 22:40:06 -080081 ReturnStats2023() ([]db.Stats2023, error)
Milo Lin1d59f0c2022-06-22 20:30:58 -070082 QueryAllShifts(int) ([]db.Shift, error)
Philipp Schrader8747f1b2022-02-23 23:56:22 -080083 QueryStats(int) ([]db.Stats, error)
Philipp Schradereecb8962022-06-01 21:02:42 -070084 QueryNotes(int32) ([]string, error)
Filip Kujawaf947cb42022-11-21 10:00:30 -080085 AddNotes(db.NotesData) error
Filip Kujawa210a03b2022-11-24 14:41:11 -080086 AddDriverRanking(db.DriverRankingData) error
Philipp Schrader8747f1b2022-02-23 23:56:22 -080087}
88
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080089// Handles unknown requests. Just returns a 404.
90func unknown(w http.ResponseWriter, req *http.Request) {
91 w.WriteHeader(http.StatusNotFound)
92}
93
94func respondWithError(w http.ResponseWriter, statusCode int, errorMessage string) {
95 builder := flatbuffers.NewBuilder(1024)
96 builder.Finish((&error_response.ErrorResponseT{
97 ErrorMessage: errorMessage,
98 }).Pack(builder))
99 w.WriteHeader(statusCode)
100 w.Write(builder.FinishedBytes())
101}
102
103func respondNotImplemented(w http.ResponseWriter) {
104 respondWithError(w, http.StatusNotImplemented, "")
105}
106
Philipp Schraderb7e75932022-03-26 16:18:34 -0700107func 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 -0800108 success := true
109 defer func() {
110 if r := recover(); r != nil {
Philipp Schraderb7e75932022-03-26 16:18:34 -0700111 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse %s: %v", requestName, r))
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800112 success = false
113 }
114 }()
Philipp Schraderb7e75932022-03-26 16:18:34 -0700115 result := parser(buf, 0)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800116 return result, success
117}
118
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700119// Parses the authorization information that the browser inserts into the
120// headers. The authorization follows this format:
121//
Philipp Schrader35bb1532023-03-05 13:49:12 -0800122// req.Headers["Authorization"] = []string{"Basic <base64 encoded username:password>"}
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700123func parseUsername(req *http.Request) string {
124 auth, ok := req.Header["Authorization"]
125 if !ok {
126 return "unknown"
127 }
128
129 parts := strings.Split(auth[0], " ")
130 if !(len(parts) == 2 && parts[0] == "Basic") {
131 return "unknown"
132 }
133
134 info, err := base64.StdEncoding.DecodeString(parts[1])
135 if err != nil {
136 log.Println("ERROR: Failed to parse Basic authentication.")
137 return "unknown"
138 }
139
140 loginParts := strings.Split(string(info), ":")
141 if len(loginParts) != 2 {
142 return "unknown"
143 }
144 return loginParts[0]
145}
146
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800147// Handles a SubmitDataScouting request.
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800148type submitDataScoutingHandler struct {
149 db Database
150}
151
152func (handler submitDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700153 // Get the username of the person submitting the data.
154 username := parseUsername(req)
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700155
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800156 requestBytes, err := io.ReadAll(req.Body)
157 if err != nil {
158 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
159 return
160 }
161
Philipp Schraderb7e75932022-03-26 16:18:34 -0700162 request, success := parseRequest[SubmitDataScouting](w, requestBytes, "SubmitDataScouting", submit_data_scouting.GetRootAsSubmitDataScouting)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800163 if !success {
164 return
165 }
166
Philipp Schraderd7b6eba2022-03-18 22:21:25 -0700167 log.Println("Got data scouting data for match", request.Match(), "team", request.Team(), "from", username)
168
Philipp Schrader30005e42022-03-06 13:53:58 -0800169 stats := db.Stats{
Philipp Schraderfee07e12022-03-17 22:19:47 -0700170 TeamNumber: request.Team(),
171 MatchNumber: request.Match(),
Philipp Schrader30b4a682022-04-16 14:36:17 -0700172 SetNumber: request.SetNumber(),
Philipp Schrader4535b7e2022-04-08 20:27:00 -0700173 CompLevel: string(request.CompLevel()),
Philipp Schraderfee07e12022-03-17 22:19:47 -0700174 StartingQuadrant: request.StartingQuadrant(),
175 AutoBallPickedUp: [5]bool{
176 request.AutoBall1(), request.AutoBall2(), request.AutoBall3(),
177 request.AutoBall4(), request.AutoBall5(),
178 },
Philipp Schraderfa45d742022-03-18 19:29:05 -0700179 ShotsMissedAuto: request.MissedShotsAuto(),
180 UpperGoalAuto: request.UpperGoalAuto(),
181 LowerGoalAuto: request.LowerGoalAuto(),
182 ShotsMissed: request.MissedShotsTele(),
183 UpperGoalShots: request.UpperGoalTele(),
184 LowerGoalShots: request.LowerGoalTele(),
185 PlayedDefense: request.DefenseRating(),
186 DefenseReceivedScore: request.DefenseReceivedRating(),
187 Climbing: int32(request.ClimbLevel()),
188 CollectedBy: username,
189 Comment: string(request.Comment()),
Philipp Schrader30005e42022-03-06 13:53:58 -0800190 }
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800191
Philipp Schraderfee07e12022-03-17 22:19:47 -0700192 // Do some error checking.
193 if stats.StartingQuadrant < 1 || stats.StartingQuadrant > 4 {
194 respondWithError(w, http.StatusBadRequest, fmt.Sprint(
195 "Invalid starting_quadrant field value of ", stats.StartingQuadrant))
196 return
197 }
198
Philipp Schrader30005e42022-03-06 13:53:58 -0800199 err = handler.db.AddToStats(stats)
200 if err != nil {
201 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit datascouting data: ", err))
Philipp Schraderfee07e12022-03-17 22:19:47 -0700202 return
Philipp Schrader30005e42022-03-06 13:53:58 -0800203 }
204
205 builder := flatbuffers.NewBuilder(50 * 1024)
206 builder.Finish((&SubmitDataScoutingResponseT{}).Pack(builder))
207 w.Write(builder.FinishedBytes())
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800208}
209
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800210// Handles a RequestAllMaches request.
211type requestAllMatchesHandler struct {
212 db Database
213}
214
Emily Markovabf24c9e2023-02-08 20:31:11 -0800215func findIndexInList(list []string, comp_level string) (int, error) {
216 for index, value := range list {
217 if value == comp_level {
218 return index, nil
219 }
220 }
221 return -1, errors.New(fmt.Sprint("Failed to find comp level ", comp_level, " in list ", list))
222}
223
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800224func (handler requestAllMatchesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
225 requestBytes, err := io.ReadAll(req.Body)
226 if err != nil {
227 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
228 return
229 }
230
Philipp Schraderb7e75932022-03-26 16:18:34 -0700231 _, success := parseRequest(w, requestBytes, "RequestAllMatches", request_all_matches.GetRootAsRequestAllMatches)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800232 if !success {
233 return
234 }
235
236 matches, err := handler.db.ReturnMatches()
237 if err != nil {
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700238 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800239 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800240 }
241
Emily Markovabf24c9e2023-02-08 20:31:11 -0800242 // Change structure of match objects in the database(1 per team) to
243 // the old match structure(1 per match) that the webserver uses.
244 type Key struct {
245 MatchNumber int32
246 SetNumber int32
247 CompLevel string
248 }
249
250 assembledMatches := map[Key]request_all_matches_response.MatchT{}
251
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800252 for _, match := range matches {
Emily Markovabf24c9e2023-02-08 20:31:11 -0800253 key := Key{match.MatchNumber, match.SetNumber, match.CompLevel}
Emily Markovabf24c9e2023-02-08 20:31:11 -0800254 entry, ok := assembledMatches[key]
255 if !ok {
256 entry = request_all_matches_response.MatchT{
257 MatchNumber: match.MatchNumber,
258 SetNumber: match.SetNumber,
259 CompLevel: match.CompLevel,
260 }
261 }
Emily Markovabf24c9e2023-02-08 20:31:11 -0800262 switch match.Alliance {
263 case "R":
264 switch match.AlliancePosition {
265 case 1:
266 entry.R1 = match.TeamNumber
267 case 2:
268 entry.R2 = match.TeamNumber
269 case 3:
270 entry.R3 = match.TeamNumber
271 default:
272 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown red position ", strconv.Itoa(int(match.AlliancePosition)), " in match ", strconv.Itoa(int(match.MatchNumber))))
273 return
274 }
275 case "B":
276 switch match.AlliancePosition {
277 case 1:
278 entry.B1 = match.TeamNumber
279 case 2:
280 entry.B2 = match.TeamNumber
281 case 3:
282 entry.B3 = match.TeamNumber
283 default:
284 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown blue position ", strconv.Itoa(int(match.AlliancePosition)), " in match ", strconv.Itoa(int(match.MatchNumber))))
285 return
286 }
287 default:
288 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Unknown alliance ", match.Alliance, " in match ", strconv.Itoa(int(match.AlliancePosition))))
289 return
290 }
291 assembledMatches[key] = entry
292 }
293
294 var response RequestAllMatchesResponseT
295 for _, match := range assembledMatches {
296 copied_match := match
297 response.MatchList = append(response.MatchList, &copied_match)
298 }
299
300 var MATCH_TYPE_ORDERING = []string{"qm", "ef", "qf", "sf", "f"}
301
302 err = nil
303 sort.Slice(response.MatchList, func(i, j int) bool {
304 if err != nil {
305 return false
306 }
307 a := response.MatchList[i]
308 b := response.MatchList[j]
309
Emily Markovaabcac6e2023-02-18 17:50:03 -0800310 aMatchTypeIndex, err2 := findIndexInList(MATCH_TYPE_ORDERING, a.CompLevel)
311 if err2 != nil {
312 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 -0800313 return false
314 }
Emily Markovaabcac6e2023-02-18 17:50:03 -0800315 bMatchTypeIndex, err2 := findIndexInList(MATCH_TYPE_ORDERING, b.CompLevel)
316 if err2 != nil {
317 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 -0800318 return false
319 }
320
321 if aMatchTypeIndex < bMatchTypeIndex {
322 return true
323 }
324 if aMatchTypeIndex > bMatchTypeIndex {
325 return false
326 }
327
328 // Then sort by match number. E.g. in semi finals, all match 1 rounds
329 // are done first. Then come match 2 rounds. And then, if necessary,
330 // the match 3 rounds.
331 aMatchNumber := a.MatchNumber
332 bMatchNumber := b.MatchNumber
333 if aMatchNumber < bMatchNumber {
334 return true
335 }
336 if aMatchNumber > bMatchNumber {
337 return false
338 }
339 // Lastly, sort by set number. I.e. Semi Final 1 Match 1 happens first.
340 // Then comes Semi Final 2 Match 1. Then comes Semi Final 1 Match 2. Then
341 // Semi Final 2 Match 2.
342 aSetNumber := a.SetNumber
343 bSetNumber := b.SetNumber
344 if aSetNumber < bSetNumber {
345 return true
346 }
347 if aSetNumber > bSetNumber {
348 return false
349 }
350 return true
351 })
352
353 if err != nil {
354 // check if error happened during sorting and notify webpage if that
355 respondWithError(w, http.StatusInternalServerError, fmt.Sprint(err))
356 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800357 }
358
359 builder := flatbuffers.NewBuilder(50 * 1024)
360 builder.Finish((&response).Pack(builder))
361 w.Write(builder.FinishedBytes())
362}
363
Philipp Schraderacf96232022-03-01 22:03:30 -0800364// Handles a RequestDataScouting request.
365type requestDataScoutingHandler struct {
366 db Database
367}
368
369func (handler requestDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
370 requestBytes, err := io.ReadAll(req.Body)
371 if err != nil {
372 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
373 return
374 }
375
Philipp Schraderb7e75932022-03-26 16:18:34 -0700376 _, success := parseRequest(w, requestBytes, "RequestDataScouting", request_data_scouting.GetRootAsRequestDataScouting)
Philipp Schraderacf96232022-03-01 22:03:30 -0800377 if !success {
378 return
379 }
380
381 stats, err := handler.db.ReturnStats()
382 if err != nil {
Emily Markova290147d2023-03-03 22:40:06 -0800383 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800384 return
Philipp Schraderacf96232022-03-01 22:03:30 -0800385 }
386
387 var response RequestDataScoutingResponseT
388 for _, stat := range stats {
389 response.StatsList = append(response.StatsList, &request_data_scouting_response.StatsT{
Philipp Schraderfa45d742022-03-18 19:29:05 -0700390 Team: stat.TeamNumber,
391 Match: stat.MatchNumber,
Philipp Schrader30b4a682022-04-16 14:36:17 -0700392 SetNumber: stat.SetNumber,
Philipp Schrader4535b7e2022-04-08 20:27:00 -0700393 CompLevel: stat.CompLevel,
Philipp Schraderfa45d742022-03-18 19:29:05 -0700394 StartingQuadrant: stat.StartingQuadrant,
395 AutoBall1: stat.AutoBallPickedUp[0],
396 AutoBall2: stat.AutoBallPickedUp[1],
397 AutoBall3: stat.AutoBallPickedUp[2],
398 AutoBall4: stat.AutoBallPickedUp[3],
399 AutoBall5: stat.AutoBallPickedUp[4],
400 MissedShotsAuto: stat.ShotsMissedAuto,
401 UpperGoalAuto: stat.UpperGoalAuto,
402 LowerGoalAuto: stat.LowerGoalAuto,
403 MissedShotsTele: stat.ShotsMissed,
404 UpperGoalTele: stat.UpperGoalShots,
405 LowerGoalTele: stat.LowerGoalShots,
406 DefenseRating: stat.PlayedDefense,
407 DefenseReceivedRating: stat.DefenseReceivedScore,
408 ClimbLevel: request_data_scouting_response.ClimbLevel(stat.Climbing),
409 CollectedBy: stat.CollectedBy,
410 Comment: stat.Comment,
Philipp Schraderacf96232022-03-01 22:03:30 -0800411 })
412 }
413
414 builder := flatbuffers.NewBuilder(50 * 1024)
415 builder.Finish((&response).Pack(builder))
416 w.Write(builder.FinishedBytes())
417}
418
Alex Perry81f96ba2022-03-13 18:26:19 -0700419type submitNoteScoutingHandler struct {
420 db Database
421}
422
423func (handler submitNoteScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
424 requestBytes, err := io.ReadAll(req.Body)
425 if err != nil {
426 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
427 return
428 }
429
Philipp Schraderb7e75932022-03-26 16:18:34 -0700430 request, success := parseRequest(w, requestBytes, "SubmitNotes", submit_notes.GetRootAsSubmitNotes)
Alex Perry81f96ba2022-03-13 18:26:19 -0700431 if !success {
432 return
433 }
434
Filip Kujawaf947cb42022-11-21 10:00:30 -0800435 err = handler.db.AddNotes(db.NotesData{
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800436 TeamNumber: request.Team(),
437 Notes: string(request.Notes()),
438 GoodDriving: bool(request.GoodDriving()),
439 BadDriving: bool(request.BadDriving()),
440 SketchyPickup: bool(request.SketchyPickup()),
441 SketchyPlacing: bool(request.SketchyPlacing()),
442 GoodDefense: bool(request.GoodDefense()),
443 BadDefense: bool(request.BadDefense()),
444 EasilyDefended: bool(request.EasilyDefended()),
Filip Kujawaf947cb42022-11-21 10:00:30 -0800445 })
Alex Perry81f96ba2022-03-13 18:26:19 -0700446 if err != nil {
447 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert notes: %v", err))
448 return
449 }
450
451 var response SubmitNotesResponseT
452 builder := flatbuffers.NewBuilder(10)
453 builder.Finish((&response).Pack(builder))
454 w.Write(builder.FinishedBytes())
455}
456
Emily Markova1abe9782023-03-11 19:45:38 -0800457func ConvertActionsToStat(submitActions *submit_actions.SubmitActions) (db.Stats2023, error) {
458 overall_time := int64(0)
459 cycles := int64(0)
460 picked_up := false
461 lastPlacedTime := int64(0)
462 stat := db.Stats2023{TeamNumber: string(submitActions.TeamNumber()), MatchNumber: submitActions.MatchNumber(), SetNumber: submitActions.SetNumber(), CompLevel: string(submitActions.CompLevel()),
463 StartingQuadrant: 0, LowCubesAuto: 0, MiddleCubesAuto: 0, HighCubesAuto: 0, CubesDroppedAuto: 0,
464 LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 0, ConesDroppedAuto: 0, LowCubes: 0, MiddleCubes: 0, HighCubes: 0,
465 CubesDropped: 0, LowCones: 0, MiddleCones: 0, HighCones: 0, ConesDropped: 0, AvgCycle: 0, CollectedBy: string(submitActions.CollectedBy()),
466 }
467 // Loop over all actions.
468 for i := 0; i < submitActions.ActionsListLength(); i++ {
469 var action submit_actions.Action
470 if !submitActions.ActionsList(&action, i) {
471 return db.Stats2023{}, errors.New(fmt.Sprintf("Failed to parse submit_actions.Action"))
472 }
473 actionTable := new(flatbuffers.Table)
474 action_type := action.ActionTakenType()
475 if !action.ActionTaken(actionTable) {
476 return db.Stats2023{}, errors.New(fmt.Sprint("Failed to parse sub-action or sub-action was missing"))
477 }
478 if action_type == submit_actions.ActionTypeStartMatchAction {
479 var startMatchAction submit_actions.StartMatchAction
480 startMatchAction.Init(actionTable.Bytes, actionTable.Pos)
481 stat.StartingQuadrant = startMatchAction.Position()
482 } else if action_type == submit_actions.ActionTypePickupObjectAction {
483 var pick_up_action submit_actions.PickupObjectAction
484 pick_up_action.Init(actionTable.Bytes, actionTable.Pos)
485 if picked_up == true {
486 object := pick_up_action.ObjectType().String()
487 auto := pick_up_action.Auto()
488 if object == "kCube" && auto == false {
489 stat.CubesDropped += 1
490 } else if object == "kCube" && auto == true {
491 stat.CubesDroppedAuto += 1
492 } else if object == "kCone" && auto == false {
493 stat.ConesDropped += 1
494 } else if object == "kCube" && auto == true {
495 stat.ConesDroppedAuto += 1
496 }
497 } else {
498 picked_up = true
499 }
500 } else if action_type == submit_actions.ActionTypePlaceObjectAction {
501 var place_action submit_actions.PlaceObjectAction
502 place_action.Init(actionTable.Bytes, actionTable.Pos)
503 if !picked_up {
504 return db.Stats2023{}, errors.New(fmt.Sprintf("Got PlaceObjectAction without corresponding PickupObjectAction"))
505 }
506 object := place_action.ObjectType()
507 level := place_action.ScoreLevel()
508 auto := place_action.Auto()
509 if object == 0 && level == 0 && auto == true {
510 stat.LowCubesAuto += 1
511 } else if object == 0 && level == 0 && auto == false {
512 stat.LowCubes += 1
513 } else if object == 0 && level == 1 && auto == true {
514 stat.MiddleCubesAuto += 1
515 } else if object == 0 && level == 1 && auto == false {
516 stat.MiddleCubes += 1
517 } else if object == 0 && level == 2 && auto == true {
518 stat.HighCubesAuto += 1
519 } else if object == 0 && level == 2 && auto == false {
520 stat.HighCubes += 1
521 } else if object == 1 && level == 0 && auto == true {
522 stat.LowConesAuto += 1
523 } else if object == 1 && level == 0 && auto == false {
524 stat.LowCones += 1
525 } else if object == 1 && level == 1 && auto == true {
526 stat.MiddleConesAuto += 1
527 } else if object == 1 && level == 1 && auto == false {
528 stat.MiddleCones += 1
529 } else if object == 1 && level == 2 && auto == true {
530 stat.HighConesAuto += 1
531 } else if object == 1 && level == 2 && auto == false {
532 stat.HighCones += 1
533 } else {
534 return db.Stats2023{}, errors.New(fmt.Sprintf("Got unknown ObjectType/ScoreLevel/Auto combination"))
535 }
536 picked_up = false
537 if lastPlacedTime != int64(0) {
538 // If this is not the first time we place,
539 // start counting cycle time. We define cycle
540 // time as the time between placements.
541 overall_time += int64(action.Timestamp()) - lastPlacedTime
542 cycles += 1
543 }
544 lastPlacedTime = int64(action.Timestamp())
545 }
546 }
547 if cycles != 0 {
548 stat.AvgCycle = int32(overall_time / cycles)
549 } else {
550 stat.AvgCycle = 0
551 }
552 return stat, nil
553}
554
Emily Markova290147d2023-03-03 22:40:06 -0800555// Handles a Request2023DataScouting request.
556type request2023DataScoutingHandler struct {
557 db Database
558}
559
560func (handler request2023DataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
561 requestBytes, err := io.ReadAll(req.Body)
562 if err != nil {
563 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
564 return
565 }
566
567 _, success := parseRequest(w, requestBytes, "Request2023DataScouting", request_2023_data_scouting.GetRootAsRequest2023DataScouting)
568 if !success {
569 return
570 }
571
572 stats, err := handler.db.ReturnStats2023()
573 if err != nil {
574 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
575 return
576 }
577
578 var response Request2023DataScoutingResponseT
579 for _, stat := range stats {
580 response.StatsList = append(response.StatsList, &request_2023_data_scouting_response.Stats2023T{
581 TeamNumber: stat.TeamNumber,
582 MatchNumber: stat.MatchNumber,
583 SetNumber: stat.SetNumber,
584 CompLevel: stat.CompLevel,
585 StartingQuadrant: stat.StartingQuadrant,
586 LowCubesAuto: stat.LowCubesAuto,
587 MiddleCubesAuto: stat.MiddleCubesAuto,
588 HighCubesAuto: stat.HighCubesAuto,
589 CubesDroppedAuto: stat.CubesDroppedAuto,
590 LowConesAuto: stat.LowConesAuto,
591 MiddleConesAuto: stat.MiddleConesAuto,
592 HighConesAuto: stat.HighConesAuto,
593 ConesDroppedAuto: stat.ConesDroppedAuto,
594 LowCubes: stat.LowCubes,
595 MiddleCubes: stat.MiddleCubes,
596 HighCubes: stat.HighCubes,
597 CubesDropped: stat.CubesDropped,
598 LowCones: stat.LowCones,
599 MiddleCones: stat.MiddleCones,
600 HighCones: stat.HighCones,
601 ConesDropped: stat.ConesDropped,
602 AvgCycle: stat.AvgCycle,
603 CollectedBy: stat.CollectedBy,
604 })
605 }
606
607 builder := flatbuffers.NewBuilder(50 * 1024)
608 builder.Finish((&response).Pack(builder))
609 w.Write(builder.FinishedBytes())
610}
611
Alex Perry81f96ba2022-03-13 18:26:19 -0700612type requestNotesForTeamHandler struct {
613 db Database
614}
615
616func (handler requestNotesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
617 requestBytes, err := io.ReadAll(req.Body)
618 if err != nil {
619 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
620 return
621 }
622
Philipp Schraderb7e75932022-03-26 16:18:34 -0700623 request, success := parseRequest(w, requestBytes, "RequestNotesForTeam", request_notes_for_team.GetRootAsRequestNotesForTeam)
Alex Perry81f96ba2022-03-13 18:26:19 -0700624 if !success {
625 return
626 }
627
Philipp Schradereecb8962022-06-01 21:02:42 -0700628 notes, err := handler.db.QueryNotes(request.Team())
Alex Perry81f96ba2022-03-13 18:26:19 -0700629 if err != nil {
630 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query notes: %v", err))
631 return
632 }
633
634 var response RequestNotesForTeamResponseT
Philipp Schradereecb8962022-06-01 21:02:42 -0700635 for _, data := range notes {
Alex Perry81f96ba2022-03-13 18:26:19 -0700636 response.Notes = append(response.Notes, &request_notes_for_team_response.NoteT{data})
637 }
638
639 builder := flatbuffers.NewBuilder(1024)
640 builder.Finish((&response).Pack(builder))
641 w.Write(builder.FinishedBytes())
642}
643
Milo Lin1d59f0c2022-06-22 20:30:58 -0700644type requestShiftScheduleHandler struct {
645 db Database
646}
647
648func (handler requestShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
649 requestBytes, err := io.ReadAll(req.Body)
650 if err != nil {
651 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
652 return
653 }
654
655 _, success := parseRequest(w, requestBytes, "RequestShiftSchedule", request_shift_schedule.GetRootAsRequestShiftSchedule)
656 if !success {
657 return
658 }
659
660 shiftData, err := handler.db.ReturnAllShifts()
661 if err != nil {
662 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query shift schedule: %v", err))
663 return
664 }
665
666 var response RequestShiftScheduleResponseT
667 for _, shifts := range shiftData {
668 response.ShiftSchedule = append(response.ShiftSchedule, &request_shift_schedule_response.MatchAssignmentT{
669 MatchNumber: shifts.MatchNumber,
670 R1scouter: shifts.R1scouter,
671 R2scouter: shifts.R2scouter,
672 R3scouter: shifts.R3scouter,
673 B1scouter: shifts.B1scouter,
674 B2scouter: shifts.B2scouter,
675 B3scouter: shifts.B3scouter,
676 })
677 }
678
679 builder := flatbuffers.NewBuilder(1024)
680 builder.Finish((&response).Pack(builder))
681 w.Write(builder.FinishedBytes())
682}
683
684type submitShiftScheduleHandler struct {
685 db Database
686}
687
688func (handler submitShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
689 // Get the username of the person submitting the data.
690 username := parseUsername(req)
691
692 requestBytes, err := io.ReadAll(req.Body)
693 if err != nil {
694 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
695 return
696 }
697
698 request, success := parseRequest[SubmitShiftSchedule](w, requestBytes, "SubmitShiftSchedule", submit_shift_schedule.GetRootAsSubmitShiftSchedule)
699 if !success {
700 return
701 }
702
703 log.Println("Got shift schedule from", username)
704 shift_schedule_length := request.ShiftScheduleLength()
705 for i := 0; i < shift_schedule_length; i++ {
706 var match_assignment submit_shift_schedule.MatchAssignment
707 request.ShiftSchedule(&match_assignment, i)
708 current_shift := db.Shift{
709 MatchNumber: match_assignment.MatchNumber(),
710 R1scouter: string(match_assignment.R1scouter()),
711 R2scouter: string(match_assignment.R2scouter()),
712 R3scouter: string(match_assignment.R3scouter()),
713 B1scouter: string(match_assignment.B1scouter()),
714 B2scouter: string(match_assignment.B2scouter()),
715 B3scouter: string(match_assignment.B3scouter()),
716 }
717 err = handler.db.AddToShift(current_shift)
718 if err != nil {
719 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit shift schedule: ", err))
720 return
721 }
722 }
723
724 builder := flatbuffers.NewBuilder(50 * 1024)
725 builder.Finish((&SubmitShiftScheduleResponseT{}).Pack(builder))
726 w.Write(builder.FinishedBytes())
727}
728
Filip Kujawa210a03b2022-11-24 14:41:11 -0800729type SubmitDriverRankingHandler struct {
730 db Database
731}
732
733func (handler SubmitDriverRankingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
734 requestBytes, err := io.ReadAll(req.Body)
735 if err != nil {
736 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
737 return
738 }
739
740 request, success := parseRequest(w, requestBytes, "SubmitDriverRanking", submit_driver_ranking.GetRootAsSubmitDriverRanking)
741 if !success {
742 return
743 }
744
745 err = handler.db.AddDriverRanking(db.DriverRankingData{
746 MatchNumber: request.MatchNumber(),
747 Rank1: request.Rank1(),
748 Rank2: request.Rank2(),
749 Rank3: request.Rank3(),
750 })
751
752 if err != nil {
753 respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to insert driver ranking: %v", err))
754 return
755 }
756
757 var response SubmitDriverRankingResponseT
758 builder := flatbuffers.NewBuilder(10)
759 builder.Finish((&response).Pack(builder))
760 w.Write(builder.FinishedBytes())
761}
762
Filip Kujawaf882e022022-12-14 13:14:08 -0800763type requestAllNotesHandler struct {
764 db Database
765}
766
767func (handler requestAllNotesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
768 requestBytes, err := io.ReadAll(req.Body)
769 if err != nil {
770 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
771 return
772 }
773
774 _, success := parseRequest(w, requestBytes, "RequestAllNotes", request_all_notes.GetRootAsRequestAllNotes)
775 if !success {
776 return
777 }
778
779 notes, err := handler.db.ReturnAllNotes()
780 if err != nil {
781 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
782 return
783 }
784
785 var response RequestAllNotesResponseT
786 for _, note := range notes {
787 response.NoteList = append(response.NoteList, &request_all_notes_response.NoteT{
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800788 Team: note.TeamNumber,
789 Notes: note.Notes,
790 GoodDriving: note.GoodDriving,
791 BadDriving: note.BadDriving,
792 SketchyPickup: note.SketchyPickup,
793 SketchyPlacing: note.SketchyPlacing,
794 GoodDefense: note.GoodDefense,
795 BadDefense: note.BadDefense,
796 EasilyDefended: note.EasilyDefended,
Filip Kujawaf882e022022-12-14 13:14:08 -0800797 })
798 }
799
800 builder := flatbuffers.NewBuilder(50 * 1024)
801 builder.Finish((&response).Pack(builder))
802 w.Write(builder.FinishedBytes())
803}
804
805type requestAllDriverRankingsHandler struct {
806 db Database
807}
808
809func (handler requestAllDriverRankingsHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
810 requestBytes, err := io.ReadAll(req.Body)
811 if err != nil {
812 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
813 return
814 }
815
816 _, success := parseRequest(w, requestBytes, "RequestAllDriverRankings", request_all_driver_rankings.GetRootAsRequestAllDriverRankings)
817 if !success {
818 return
819 }
820
821 rankings, err := handler.db.ReturnAllDriverRankings()
822 if err != nil {
823 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
824 return
825 }
826
827 var response RequestAllDriverRankingsResponseT
828 for _, ranking := range rankings {
829 response.DriverRankingList = append(response.DriverRankingList, &request_all_driver_rankings_response.RankingT{
830 MatchNumber: ranking.MatchNumber,
831 Rank1: ranking.Rank1,
832 Rank2: ranking.Rank2,
833 Rank3: ranking.Rank3,
834 })
835 }
836
837 builder := flatbuffers.NewBuilder(50 * 1024)
838 builder.Finish((&response).Pack(builder))
839 w.Write(builder.FinishedBytes())
840}
841
Philipp Schrader43c730b2023-02-26 20:27:44 -0800842func HandleRequests(db Database, scoutingServer server.ScoutingServer) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800843 scoutingServer.HandleFunc("/requests", unknown)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800844 scoutingServer.Handle("/requests/submit/data_scouting", submitDataScoutingHandler{db})
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800845 scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
Filip Kujawaf882e022022-12-14 13:14:08 -0800846 scoutingServer.Handle("/requests/request/all_notes", requestAllNotesHandler{db})
847 scoutingServer.Handle("/requests/request/all_driver_rankings", requestAllDriverRankingsHandler{db})
Philipp Schraderacf96232022-03-01 22:03:30 -0800848 scoutingServer.Handle("/requests/request/data_scouting", requestDataScoutingHandler{db})
Emily Markova290147d2023-03-03 22:40:06 -0800849 scoutingServer.Handle("/requests/request/2023_data_scouting", request2023DataScoutingHandler{db})
Alex Perry81f96ba2022-03-13 18:26:19 -0700850 scoutingServer.Handle("/requests/submit/submit_notes", submitNoteScoutingHandler{db})
851 scoutingServer.Handle("/requests/request/notes_for_team", requestNotesForTeamHandler{db})
Milo Lin1d59f0c2022-06-22 20:30:58 -0700852 scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
853 scoutingServer.Handle("/requests/request/shift_schedule", requestShiftScheduleHandler{db})
Filip Kujawa210a03b2022-11-24 14:41:11 -0800854 scoutingServer.Handle("/requests/submit/submit_driver_ranking", SubmitDriverRankingHandler{db})
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800855}