scouting: adding support for requests with shift schedule

Signed-off-by: Milo Lin <100027790@mvla.net>
Change-Id: I653e2874b1c74a9ac65105f05dd3af00dcca8fb9
diff --git a/scouting/webserver/requests/BUILD b/scouting/webserver/requests/BUILD
index 744772c..87575a9 100644
--- a/scouting/webserver/requests/BUILD
+++ b/scouting/webserver/requests/BUILD
@@ -20,10 +20,14 @@
         "//scouting/webserver/requests/messages:request_matches_for_team_response_go_fbs",
         "//scouting/webserver/requests/messages:request_notes_for_team_go_fbs",
         "//scouting/webserver/requests/messages:request_notes_for_team_response_go_fbs",
+        "//scouting/webserver/requests/messages:request_shift_schedule_go_fbs",
+        "//scouting/webserver/requests/messages:request_shift_schedule_response_go_fbs",
         "//scouting/webserver/requests/messages:submit_data_scouting_go_fbs",
         "//scouting/webserver/requests/messages:submit_data_scouting_response_go_fbs",
         "//scouting/webserver/requests/messages:submit_notes_go_fbs",
         "//scouting/webserver/requests/messages:submit_notes_response_go_fbs",
+        "//scouting/webserver/requests/messages:submit_shift_schedule_go_fbs",
+        "//scouting/webserver/requests/messages:submit_shift_schedule_response_go_fbs",
         "//scouting/webserver/server",
         "@com_github_google_flatbuffers//go:go_default_library",
     ],
@@ -48,9 +52,12 @@
         "//scouting/webserver/requests/messages:request_matches_for_team_go_fbs",
         "//scouting/webserver/requests/messages:request_matches_for_team_response_go_fbs",
         "//scouting/webserver/requests/messages:request_notes_for_team_go_fbs",
+        "//scouting/webserver/requests/messages:request_shift_schedule_go_fbs",
+        "//scouting/webserver/requests/messages:request_shift_schedule_response_go_fbs",
         "//scouting/webserver/requests/messages:submit_data_scouting_go_fbs",
         "//scouting/webserver/requests/messages:submit_data_scouting_response_go_fbs",
         "//scouting/webserver/requests/messages:submit_notes_go_fbs",
+        "//scouting/webserver/requests/messages:submit_shift_schedule_go_fbs",
         "//scouting/webserver/server",
         "@com_github_google_flatbuffers//go:go_default_library",
     ],
diff --git a/scouting/webserver/requests/debug/BUILD b/scouting/webserver/requests/debug/BUILD
index 6adaee5..04c4ffa 100644
--- a/scouting/webserver/requests/debug/BUILD
+++ b/scouting/webserver/requests/debug/BUILD
@@ -13,8 +13,10 @@
         "//scouting/webserver/requests/messages:request_data_scouting_response_go_fbs",
         "//scouting/webserver/requests/messages:request_matches_for_team_response_go_fbs",
         "//scouting/webserver/requests/messages:request_notes_for_team_response_go_fbs",
+        "//scouting/webserver/requests/messages:request_shift_schedule_response_go_fbs",
         "//scouting/webserver/requests/messages:submit_data_scouting_response_go_fbs",
         "//scouting/webserver/requests/messages:submit_notes_response_go_fbs",
+        "//scouting/webserver/requests/messages:submit_shift_schedule_response_go_fbs",
         "@com_github_google_flatbuffers//go:go_default_library",
     ],
 )
diff --git a/scouting/webserver/requests/debug/debug.go b/scouting/webserver/requests/debug/debug.go
index 21f0f05..b3df518 100644
--- a/scouting/webserver/requests/debug/debug.go
+++ b/scouting/webserver/requests/debug/debug.go
@@ -15,8 +15,10 @@
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_data_scouting_response"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team_response"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team_response"
+	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule_response"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
+	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule_response"
 	flatbuffers "github.com/google/flatbuffers/go"
 )
 
@@ -143,3 +145,15 @@
 		server+"/requests/request/notes_for_team", requestBytes,
 		request_notes_for_team_response.GetRootAsRequestNotesForTeamResponse)
 }
+
+func RequestShiftSchedule(server string, requestBytes []byte) (*request_shift_schedule_response.RequestShiftScheduleResponseT, error) {
+	return sendMessage[request_shift_schedule_response.RequestShiftScheduleResponseT](
+		server+"/requests/request/shift_schedule", requestBytes,
+		request_shift_schedule_response.GetRootAsRequestShiftScheduleResponse)
+}
+
+func SubmitShiftSchedule(server string, requestBytes []byte) (*submit_shift_schedule_response.SubmitShiftScheduleResponseT, error) {
+	return sendMessage[submit_shift_schedule_response.SubmitShiftScheduleResponseT](
+		server+"/requests/submit/shift_schedule", requestBytes,
+		submit_shift_schedule_response.GetRootAsSubmitShiftScheduleResponse)
+}
diff --git a/scouting/webserver/requests/messages/BUILD b/scouting/webserver/requests/messages/BUILD
index f7e194b..d804ea6 100644
--- a/scouting/webserver/requests/messages/BUILD
+++ b/scouting/webserver/requests/messages/BUILD
@@ -16,6 +16,10 @@
     "submit_notes_response",
     "request_notes_for_team",
     "request_notes_for_team_response",
+    "request_shift_schedule",
+    "request_shift_schedule_response",
+    "submit_shift_schedule",
+    "submit_shift_schedule_response",
 )
 
 filegroup(
diff --git a/scouting/webserver/requests/messages/request_shift_schedule.fbs b/scouting/webserver/requests/messages/request_shift_schedule.fbs
new file mode 100644
index 0000000..8f2a61c
--- /dev/null
+++ b/scouting/webserver/requests/messages/request_shift_schedule.fbs
@@ -0,0 +1,6 @@
+namespace scouting.webserver.requests;
+
+table RequestShiftSchedule {
+}
+
+root_type RequestShiftSchedule;
\ No newline at end of file
diff --git a/scouting/webserver/requests/messages/request_shift_schedule_response.fbs b/scouting/webserver/requests/messages/request_shift_schedule_response.fbs
new file mode 100644
index 0000000..611db49
--- /dev/null
+++ b/scouting/webserver/requests/messages/request_shift_schedule_response.fbs
@@ -0,0 +1,17 @@
+namespace scouting.webserver.requests;
+
+table MatchAssignment {
+    match_number:int (id:0);
+    R1scouter:string (id:1);
+    R2scouter:string (id:2);
+    R3scouter:string (id:3);
+    B1scouter:string (id:4);
+    B2scouter:string (id:5);
+    B3scouter:string (id:6);
+}
+
+table RequestShiftScheduleResponse {
+    shift_schedule:[MatchAssignment] (id:0);
+}
+
+root_type RequestShiftScheduleResponse;
\ No newline at end of file
diff --git a/scouting/webserver/requests/messages/submit_shift_schedule.fbs b/scouting/webserver/requests/messages/submit_shift_schedule.fbs
new file mode 100644
index 0000000..1f1833e
--- /dev/null
+++ b/scouting/webserver/requests/messages/submit_shift_schedule.fbs
@@ -0,0 +1,17 @@
+namespace scouting.webserver.requests;
+
+table MatchAssignment {
+    match_number:int (id:0);
+    R1scouter:string (id:1);
+    R2scouter:string (id:2);
+    R3scouter:string (id:3);
+    B1scouter:string (id:4);
+    B2scouter:string (id:5);
+    B3scouter:string (id:6);
+}
+
+table SubmitShiftSchedule {
+    shift_schedule:[MatchAssignment] (id:0);
+}
+
+root_type SubmitShiftSchedule;
\ No newline at end of file
diff --git a/scouting/webserver/requests/messages/submit_shift_schedule_response.fbs b/scouting/webserver/requests/messages/submit_shift_schedule_response.fbs
new file mode 100644
index 0000000..17eadbc
--- /dev/null
+++ b/scouting/webserver/requests/messages/submit_shift_schedule_response.fbs
@@ -0,0 +1,6 @@
+namespace scouting.webserver.requests;
+
+table SubmitShiftScheduleResponse {
+}
+
+root_type SubmitShiftScheduleResponse;
\ No newline at end of file
diff --git a/scouting/webserver/requests/requests.go b/scouting/webserver/requests/requests.go
index cef87aa..67a1722 100644
--- a/scouting/webserver/requests/requests.go
+++ b/scouting/webserver/requests/requests.go
@@ -23,10 +23,14 @@
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team_response"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team_response"
+	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule"
+	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule_response"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes_response"
+	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule"
+	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule_response"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/server"
 	flatbuffers "github.com/google/flatbuffers/go"
 )
@@ -45,15 +49,22 @@
 type SubmitNotesResponseT = submit_notes_response.SubmitNotesResponseT
 type RequestNotesForTeam = request_notes_for_team.RequestNotesForTeam
 type RequestNotesForTeamResponseT = request_notes_for_team_response.RequestNotesForTeamResponseT
+type RequestShiftSchedule = request_shift_schedule.RequestShiftSchedule
+type RequestShiftScheduleResponseT = request_shift_schedule_response.RequestShiftScheduleResponseT
+type SubmitShiftSchedule = submit_shift_schedule.SubmitShiftSchedule
+type SubmitShiftScheduleResponseT = submit_shift_schedule_response.SubmitShiftScheduleResponseT
 
 // The interface we expect the database abstraction to conform to.
 // We use an interface here because it makes unit testing easier.
 type Database interface {
 	AddToMatch(db.Match) error
+	AddToShift(db.Shift) error
 	AddToStats(db.Stats) error
 	ReturnMatches() ([]db.Match, error)
+	ReturnAllShifts() ([]db.Shift, error)
 	ReturnStats() ([]db.Stats, error)
 	QueryMatches(int32) ([]db.Match, error)
+	QueryAllShifts(int) ([]db.Shift, error)
 	QueryStats(int) ([]db.Stats, error)
 	QueryNotes(int32) (db.NotesData, error)
 	AddNotes(db.NotesData) error
@@ -480,6 +491,91 @@
 	w.Write(builder.FinishedBytes())
 }
 
+type requestShiftScheduleHandler struct {
+	db Database
+}
+
+func (handler requestShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	requestBytes, err := io.ReadAll(req.Body)
+	if err != nil {
+		respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
+		return
+	}
+
+	_, success := parseRequest(w, requestBytes, "RequestShiftSchedule", request_shift_schedule.GetRootAsRequestShiftSchedule)
+	if !success {
+		return
+	}
+
+	shiftData, err := handler.db.ReturnAllShifts()
+	if err != nil {
+		respondWithError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to query shift schedule: %v", err))
+		return
+	}
+
+	var response RequestShiftScheduleResponseT
+	for _, shifts := range shiftData {
+		response.ShiftSchedule = append(response.ShiftSchedule, &request_shift_schedule_response.MatchAssignmentT{
+			MatchNumber: shifts.MatchNumber,
+			R1scouter:   shifts.R1scouter,
+			R2scouter:   shifts.R2scouter,
+			R3scouter:   shifts.R3scouter,
+			B1scouter:   shifts.B1scouter,
+			B2scouter:   shifts.B2scouter,
+			B3scouter:   shifts.B3scouter,
+		})
+	}
+
+	builder := flatbuffers.NewBuilder(1024)
+	builder.Finish((&response).Pack(builder))
+	w.Write(builder.FinishedBytes())
+}
+
+type submitShiftScheduleHandler struct {
+	db Database
+}
+
+func (handler submitShiftScheduleHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	// Get the username of the person submitting the data.
+	username := parseUsername(req)
+
+	requestBytes, err := io.ReadAll(req.Body)
+	if err != nil {
+		respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
+		return
+	}
+
+	request, success := parseRequest[SubmitShiftSchedule](w, requestBytes, "SubmitShiftSchedule", submit_shift_schedule.GetRootAsSubmitShiftSchedule)
+	if !success {
+		return
+	}
+
+	log.Println("Got shift schedule from", username)
+	shift_schedule_length := request.ShiftScheduleLength()
+	for i := 0; i < shift_schedule_length; i++ {
+		var match_assignment submit_shift_schedule.MatchAssignment
+		request.ShiftSchedule(&match_assignment, i)
+		current_shift := db.Shift{
+			MatchNumber: match_assignment.MatchNumber(),
+			R1scouter:   string(match_assignment.R1scouter()),
+			R2scouter:   string(match_assignment.R2scouter()),
+			R3scouter:   string(match_assignment.R3scouter()),
+			B1scouter:   string(match_assignment.B1scouter()),
+			B2scouter:   string(match_assignment.B2scouter()),
+			B3scouter:   string(match_assignment.B3scouter()),
+		}
+		err = handler.db.AddToShift(current_shift)
+		if err != nil {
+			respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to submit shift schedule: ", err))
+			return
+		}
+	}
+
+	builder := flatbuffers.NewBuilder(50 * 1024)
+	builder.Finish((&SubmitShiftScheduleResponseT{}).Pack(builder))
+	w.Write(builder.FinishedBytes())
+}
+
 func HandleRequests(db Database, scrape ScrapeMatchList, scoutingServer server.ScoutingServer) {
 	scoutingServer.HandleFunc("/requests", unknown)
 	scoutingServer.Handle("/requests/submit/data_scouting", submitDataScoutingHandler{db})
@@ -489,4 +585,6 @@
 	scoutingServer.Handle("/requests/refresh_match_list", refreshMatchListHandler{db, scrape})
 	scoutingServer.Handle("/requests/submit/submit_notes", submitNoteScoutingHandler{db})
 	scoutingServer.Handle("/requests/request/notes_for_team", requestNotesForTeamHandler{db})
+	scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
+	scoutingServer.Handle("/requests/request/shift_schedule", requestShiftScheduleHandler{db})
 }
diff --git a/scouting/webserver/requests/requests_test.go b/scouting/webserver/requests/requests_test.go
index 4dda143..44fd5db 100644
--- a/scouting/webserver/requests/requests_test.go
+++ b/scouting/webserver/requests/requests_test.go
@@ -20,9 +20,12 @@
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team_response"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team"
+	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule"
+	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_shift_schedule_response"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes"
+	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_shift_schedule"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/server"
 	flatbuffers "github.com/google/flatbuffers/go"
 )
@@ -355,6 +358,113 @@
 	}
 }
 
+func TestRequestShiftSchedule(t *testing.T) {
+	db := MockDatabase{
+		shiftSchedule: []db.Shift{
+			{
+				MatchNumber: 1,
+				R1scouter:   "Bob",
+				R2scouter:   "James",
+				R3scouter:   "Robert",
+				B1scouter:   "Alice",
+				B2scouter:   "Mary",
+				B3scouter:   "Patricia",
+			},
+			{
+				MatchNumber: 2,
+				R1scouter:   "Liam",
+				R2scouter:   "Noah",
+				R3scouter:   "Oliver",
+				B1scouter:   "Emma",
+				B2scouter:   "Charlotte",
+				B3scouter:   "Amelia",
+			},
+		},
+	}
+	scoutingServer := server.NewScoutingServer()
+	HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
+	scoutingServer.Start(8080)
+	defer scoutingServer.Stop()
+
+	builder := flatbuffers.NewBuilder(1024)
+	builder.Finish((&request_shift_schedule.RequestShiftScheduleT{}).Pack(builder))
+
+	response, err := debug.RequestShiftSchedule("http://localhost:8080", builder.FinishedBytes())
+	if err != nil {
+		t.Fatal("Failed to request shift schedule: ", err)
+	}
+
+	expected := request_shift_schedule_response.RequestShiftScheduleResponseT{
+		ShiftSchedule: []*request_shift_schedule_response.MatchAssignmentT{
+			{
+				MatchNumber: 1,
+				R1scouter:   "Bob",
+				R2scouter:   "James",
+				R3scouter:   "Robert",
+				B1scouter:   "Alice",
+				B2scouter:   "Mary",
+				B3scouter:   "Patricia",
+			},
+			{
+				MatchNumber: 2,
+				R1scouter:   "Liam",
+				R2scouter:   "Noah",
+				R3scouter:   "Oliver",
+				B1scouter:   "Emma",
+				B2scouter:   "Charlotte",
+				B3scouter:   "Amelia",
+			},
+		},
+	}
+	if len(expected.ShiftSchedule) != len(response.ShiftSchedule) {
+		t.Fatal("Expected ", expected, ", but got ", *response)
+	}
+	for i, match := range expected.ShiftSchedule {
+		if !reflect.DeepEqual(*match, *response.ShiftSchedule[i]) {
+			t.Fatal("Expected for shift schedule", i, ":", *match, ", but got:", *response.ShiftSchedule[i])
+		}
+	}
+}
+
+func TestSubmitShiftSchedule(t *testing.T) {
+	database := MockDatabase{}
+	scoutingServer := server.NewScoutingServer()
+	HandleRequests(&database, scrapeEmtpyMatchList, scoutingServer)
+	scoutingServer.Start(8080)
+	defer scoutingServer.Stop()
+
+	builder := flatbuffers.NewBuilder(1024)
+	builder.Finish((&submit_shift_schedule.SubmitShiftScheduleT{
+		ShiftSchedule: []*submit_shift_schedule.MatchAssignmentT{
+			{MatchNumber: 1,
+				R1scouter: "Bob",
+				R2scouter: "James",
+				R3scouter: "Robert",
+				B1scouter: "Alice",
+				B2scouter: "Mary",
+				B3scouter: "Patricia"},
+		},
+	}).Pack(builder))
+
+	_, err := debug.SubmitShiftSchedule("http://localhost:8080", builder.FinishedBytes())
+	if err != nil {
+		t.Fatal("Failed to submit shift schedule: ", err)
+	}
+
+	expected := []db.Shift{
+		{MatchNumber: 1,
+			R1scouter: "Bob",
+			R2scouter: "James",
+			R3scouter: "Robert",
+			B1scouter: "Alice",
+			B2scouter: "Mary",
+			B3scouter: "Patricia"},
+	}
+	if !reflect.DeepEqual(expected, database.shiftSchedule) {
+		t.Fatal("Expected ", expected, ", but got:", database.shiftSchedule)
+	}
+}
+
 // Validates that we can download the schedule from The Blue Alliance.
 func TestRefreshMatchList(t *testing.T) {
 	scrapeMockSchedule := func(int32, string) ([]scraping.Match, error) {
@@ -433,9 +543,10 @@
 // needed for your tests.
 
 type MockDatabase struct {
-	matches []db.Match
-	stats   []db.Stats
-	notes   []db.NotesData
+	matches       []db.Match
+	stats         []db.Stats
+	notes         []db.NotesData
+	shiftSchedule []db.Shift
 }
 
 func (database *MockDatabase) AddToMatch(match db.Match) error {
@@ -488,6 +599,19 @@
 	return nil
 }
 
+func (database *MockDatabase) AddToShift(data db.Shift) error {
+	database.shiftSchedule = append(database.shiftSchedule, data)
+	return nil
+}
+
+func (database *MockDatabase) ReturnAllShifts() ([]db.Shift, error) {
+	return database.shiftSchedule, nil
+}
+
+func (database *MockDatabase) QueryAllShifts(int) ([]db.Shift, error) {
+	return []db.Shift{}, nil
+}
+
 // Returns an empty match list from the fake The Blue Alliance scraping.
 func scrapeEmtpyMatchList(int32, string) ([]scraping.Match, error) {
 	return nil, nil