blob: 3c21402d2f77ebdd76aca9f22f5d5f8cb849c78b [file] [log] [blame]
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08001package requests
2
3import (
4 "fmt"
5 "io"
6 "net/http"
7
Philipp Schrader8747f1b2022-02-23 23:56:22 -08008 "github.com/frc971/971-Robot-Code/scouting/db"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08009 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080010 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches"
11 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches_response"
Philipp Schraderacf96232022-03-01 22:03:30 -080012 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_data_scouting"
13 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_data_scouting_response"
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080014 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team"
15 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080016 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting"
17 _ "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
18 "github.com/frc971/971-Robot-Code/scouting/webserver/server"
19 flatbuffers "github.com/google/flatbuffers/go"
20)
21
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080022type SubmitDataScouting = submit_data_scouting.SubmitDataScouting
23type RequestAllMatches = request_all_matches.RequestAllMatches
24type RequestAllMatchesResponseT = request_all_matches_response.RequestAllMatchesResponseT
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080025type RequestMatchesForTeam = request_matches_for_team.RequestMatchesForTeam
26type RequestMatchesForTeamResponseT = request_matches_for_team_response.RequestMatchesForTeamResponseT
Philipp Schraderacf96232022-03-01 22:03:30 -080027type RequestDataScouting = request_data_scouting.RequestDataScouting
28type RequestDataScoutingResponseT = request_data_scouting_response.RequestDataScoutingResponseT
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080029
Philipp Schrader8747f1b2022-02-23 23:56:22 -080030// The interface we expect the database abstraction to conform to.
31// We use an interface here because it makes unit testing easier.
32type Database interface {
33 AddToMatch(db.Match) error
34 AddToStats(db.Stats) error
35 ReturnMatches() ([]db.Match, error)
36 ReturnStats() ([]db.Stats, error)
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080037 QueryMatches(int32) ([]db.Match, error)
Philipp Schrader8747f1b2022-02-23 23:56:22 -080038 QueryStats(int) ([]db.Stats, error)
39}
40
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080041// Handles unknown requests. Just returns a 404.
42func unknown(w http.ResponseWriter, req *http.Request) {
43 w.WriteHeader(http.StatusNotFound)
44}
45
46func respondWithError(w http.ResponseWriter, statusCode int, errorMessage string) {
47 builder := flatbuffers.NewBuilder(1024)
48 builder.Finish((&error_response.ErrorResponseT{
49 ErrorMessage: errorMessage,
50 }).Pack(builder))
51 w.WriteHeader(statusCode)
52 w.Write(builder.FinishedBytes())
53}
54
55func respondNotImplemented(w http.ResponseWriter) {
56 respondWithError(w, http.StatusNotImplemented, "")
57}
58
59// TODO(phil): Can we turn this into a generic?
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080060func parseSubmitDataScouting(w http.ResponseWriter, buf []byte) (*SubmitDataScouting, bool) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080061 success := true
62 defer func() {
63 if r := recover(); r != nil {
64 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse SubmitDataScouting: %v", r))
65 success = false
66 }
67 }()
68 result := submit_data_scouting.GetRootAsSubmitDataScouting(buf, 0)
69 return result, success
70}
71
72// Handles a SubmitDataScouting request.
Philipp Schrader8747f1b2022-02-23 23:56:22 -080073type submitDataScoutingHandler struct {
74 db Database
75}
76
77func (handler submitDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080078 requestBytes, err := io.ReadAll(req.Body)
79 if err != nil {
80 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
81 return
82 }
83
84 _, success := parseSubmitDataScouting(w, requestBytes)
85 if !success {
86 return
87 }
88
89 // TODO(phil): Actually handle the request.
Philipp Schrader8747f1b2022-02-23 23:56:22 -080090 // We have access to the database via "handler.db" here. For example:
91 // stats := handler.db.ReturnStats()
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080092
93 respondNotImplemented(w)
94}
95
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080096// TODO(phil): Can we turn this into a generic?
97func parseRequestAllMatches(w http.ResponseWriter, buf []byte) (*RequestAllMatches, bool) {
98 success := true
99 defer func() {
100 if r := recover(); r != nil {
101 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse SubmitDataScouting: %v", r))
102 success = false
103 }
104 }()
105 result := request_all_matches.GetRootAsRequestAllMatches(buf, 0)
106 return result, success
107}
108
109// Handles a RequestAllMaches request.
110type requestAllMatchesHandler struct {
111 db Database
112}
113
114func (handler requestAllMatchesHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
115 requestBytes, err := io.ReadAll(req.Body)
116 if err != nil {
117 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
118 return
119 }
120
121 _, success := parseRequestAllMatches(w, requestBytes)
122 if !success {
123 return
124 }
125
126 matches, err := handler.db.ReturnMatches()
127 if err != nil {
128 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
129 }
130
131 var response RequestAllMatchesResponseT
132 for _, match := range matches {
133 response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
134 MatchNumber: match.MatchNumber,
135 Round: match.Round,
136 CompLevel: match.CompLevel,
137 R1: match.R1,
138 R2: match.R2,
139 R3: match.R3,
140 B1: match.B1,
141 B2: match.B2,
142 B3: match.B3,
143 })
144 }
145
146 builder := flatbuffers.NewBuilder(50 * 1024)
147 builder.Finish((&response).Pack(builder))
148 w.Write(builder.FinishedBytes())
149}
150
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800151// TODO(phil): Can we turn this into a generic?
152func parseRequestMatchesForTeam(w http.ResponseWriter, buf []byte) (*RequestMatchesForTeam, bool) {
153 success := true
154 defer func() {
155 if r := recover(); r != nil {
156 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse SubmitDataScouting: %v", r))
157 success = false
158 }
159 }()
160 result := request_matches_for_team.GetRootAsRequestMatchesForTeam(buf, 0)
161 return result, success
162}
163
164// Handles a RequestMatchesForTeam request.
165type requestMatchesForTeamHandler struct {
166 db Database
167}
168
169func (handler requestMatchesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
170 requestBytes, err := io.ReadAll(req.Body)
171 if err != nil {
172 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
173 return
174 }
175
176 request, success := parseRequestMatchesForTeam(w, requestBytes)
177 if !success {
178 return
179 }
180
181 matches, err := handler.db.QueryMatches(request.Team())
182 if err != nil {
183 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
184 }
185
186 var response RequestAllMatchesResponseT
187 for _, match := range matches {
188 response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
189 MatchNumber: match.MatchNumber,
190 Round: match.Round,
191 CompLevel: match.CompLevel,
192 R1: match.R1,
193 R2: match.R2,
194 R3: match.R3,
195 B1: match.B1,
196 B2: match.B2,
197 B3: match.B3,
198 })
199 }
200
201 builder := flatbuffers.NewBuilder(50 * 1024)
202 builder.Finish((&response).Pack(builder))
203 w.Write(builder.FinishedBytes())
204}
205
Philipp Schraderacf96232022-03-01 22:03:30 -0800206// TODO(phil): Can we turn this into a generic?
207func parseRequestDataScouting(w http.ResponseWriter, buf []byte) (*RequestDataScouting, bool) {
208 success := true
209 defer func() {
210 if r := recover(); r != nil {
211 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse SubmitDataScouting: %v", r))
212 success = false
213 }
214 }()
215 result := request_data_scouting.GetRootAsRequestDataScouting(buf, 0)
216 return result, success
217}
218
219// Handles a RequestDataScouting request.
220type requestDataScoutingHandler struct {
221 db Database
222}
223
224func (handler requestDataScoutingHandler) 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
231 _, success := parseRequestDataScouting(w, requestBytes)
232 if !success {
233 return
234 }
235
236 stats, err := handler.db.ReturnStats()
237 if err != nil {
238 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
239 }
240
241 var response RequestDataScoutingResponseT
242 for _, stat := range stats {
243 response.StatsList = append(response.StatsList, &request_data_scouting_response.StatsT{
244 Team: stat.TeamNumber,
245 Match: stat.MatchNumber,
246 MissedShotsAuto: stat.ShotsMissedAuto,
247 UpperGoalAuto: stat.UpperGoalAuto,
248 LowerGoalAuto: stat.LowerGoalAuto,
249 MissedShotsTele: stat.ShotsMissed,
250 UpperGoalTele: stat.UpperGoalShots,
251 LowerGoalTele: stat.LowerGoalShots,
252 DefenseRating: stat.PlayedDefense,
253 Climbing: stat.Climbing,
254 })
255 }
256
257 builder := flatbuffers.NewBuilder(50 * 1024)
258 builder.Finish((&response).Pack(builder))
259 w.Write(builder.FinishedBytes())
260}
261
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800262func HandleRequests(db Database, scoutingServer server.ScoutingServer) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800263 scoutingServer.HandleFunc("/requests", unknown)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800264 scoutingServer.Handle("/requests/submit/data_scouting", submitDataScoutingHandler{db})
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800265 scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800266 scoutingServer.Handle("/requests/request/matches_for_team", requestMatchesForTeamHandler{db})
Philipp Schraderacf96232022-03-01 22:03:30 -0800267 scoutingServer.Handle("/requests/request/data_scouting", requestDataScoutingHandler{db})
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800268}