blob: b3e03ff002756eefe0fd95250ab6d80da8f85de2 [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))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800129 return
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800130 }
131
132 var response RequestAllMatchesResponseT
133 for _, match := range matches {
134 response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
135 MatchNumber: match.MatchNumber,
136 Round: match.Round,
137 CompLevel: match.CompLevel,
138 R1: match.R1,
139 R2: match.R2,
140 R3: match.R3,
141 B1: match.B1,
142 B2: match.B2,
143 B3: match.B3,
144 })
145 }
146
147 builder := flatbuffers.NewBuilder(50 * 1024)
148 builder.Finish((&response).Pack(builder))
149 w.Write(builder.FinishedBytes())
150}
151
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800152// TODO(phil): Can we turn this into a generic?
153func parseRequestMatchesForTeam(w http.ResponseWriter, buf []byte) (*RequestMatchesForTeam, bool) {
154 success := true
155 defer func() {
156 if r := recover(); r != nil {
157 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse SubmitDataScouting: %v", r))
158 success = false
159 }
160 }()
161 result := request_matches_for_team.GetRootAsRequestMatchesForTeam(buf, 0)
162 return result, success
163}
164
165// Handles a RequestMatchesForTeam request.
166type requestMatchesForTeamHandler struct {
167 db Database
168}
169
170func (handler requestMatchesForTeamHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
171 requestBytes, err := io.ReadAll(req.Body)
172 if err != nil {
173 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
174 return
175 }
176
177 request, success := parseRequestMatchesForTeam(w, requestBytes)
178 if !success {
179 return
180 }
181
182 matches, err := handler.db.QueryMatches(request.Team())
183 if err != nil {
184 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800185 return
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800186 }
187
188 var response RequestAllMatchesResponseT
189 for _, match := range matches {
190 response.MatchList = append(response.MatchList, &request_all_matches_response.MatchT{
191 MatchNumber: match.MatchNumber,
192 Round: match.Round,
193 CompLevel: match.CompLevel,
194 R1: match.R1,
195 R2: match.R2,
196 R3: match.R3,
197 B1: match.B1,
198 B2: match.B2,
199 B3: match.B3,
200 })
201 }
202
203 builder := flatbuffers.NewBuilder(50 * 1024)
204 builder.Finish((&response).Pack(builder))
205 w.Write(builder.FinishedBytes())
206}
207
Philipp Schraderacf96232022-03-01 22:03:30 -0800208// TODO(phil): Can we turn this into a generic?
209func parseRequestDataScouting(w http.ResponseWriter, buf []byte) (*RequestDataScouting, bool) {
210 success := true
211 defer func() {
212 if r := recover(); r != nil {
213 respondWithError(w, http.StatusBadRequest, fmt.Sprintf("Failed to parse SubmitDataScouting: %v", r))
214 success = false
215 }
216 }()
217 result := request_data_scouting.GetRootAsRequestDataScouting(buf, 0)
218 return result, success
219}
220
221// Handles a RequestDataScouting request.
222type requestDataScoutingHandler struct {
223 db Database
224}
225
226func (handler requestDataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
227 requestBytes, err := io.ReadAll(req.Body)
228 if err != nil {
229 respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
230 return
231 }
232
233 _, success := parseRequestDataScouting(w, requestBytes)
234 if !success {
235 return
236 }
237
238 stats, err := handler.db.ReturnStats()
239 if err != nil {
240 respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
Philipp Schrader2e7eb0002022-03-02 22:52:39 -0800241 return
Philipp Schraderacf96232022-03-01 22:03:30 -0800242 }
243
244 var response RequestDataScoutingResponseT
245 for _, stat := range stats {
246 response.StatsList = append(response.StatsList, &request_data_scouting_response.StatsT{
247 Team: stat.TeamNumber,
248 Match: stat.MatchNumber,
249 MissedShotsAuto: stat.ShotsMissedAuto,
250 UpperGoalAuto: stat.UpperGoalAuto,
251 LowerGoalAuto: stat.LowerGoalAuto,
252 MissedShotsTele: stat.ShotsMissed,
253 UpperGoalTele: stat.UpperGoalShots,
254 LowerGoalTele: stat.LowerGoalShots,
255 DefenseRating: stat.PlayedDefense,
256 Climbing: stat.Climbing,
257 })
258 }
259
260 builder := flatbuffers.NewBuilder(50 * 1024)
261 builder.Finish((&response).Pack(builder))
262 w.Write(builder.FinishedBytes())
263}
264
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800265func HandleRequests(db Database, scoutingServer server.ScoutingServer) {
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800266 scoutingServer.HandleFunc("/requests", unknown)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800267 scoutingServer.Handle("/requests/submit/data_scouting", submitDataScoutingHandler{db})
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800268 scoutingServer.Handle("/requests/request/all_matches", requestAllMatchesHandler{db})
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800269 scoutingServer.Handle("/requests/request/matches_for_team", requestMatchesForTeamHandler{db})
Philipp Schraderacf96232022-03-01 22:03:30 -0800270 scoutingServer.Handle("/requests/request/data_scouting", requestDataScoutingHandler{db})
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800271}