Add Pit Scouting Tab

Signed-off-by: Emily Markova <emily.markova@gmail.com>
Change-Id: Iede446546e20f2915bb53e134050b5025976da36
diff --git a/scouting/db/BUILD b/scouting/db/BUILD
index 154cab6..9447a5f 100644
--- a/scouting/db/BUILD
+++ b/scouting/db/BUILD
@@ -18,9 +18,7 @@
     name = "db_test",
     size = "small",
     srcs = ["db_test.go"],
-    data = [
-        "//scouting/db/testdb_server",
-    ],
+    data = ["//scouting/db/testdb_server"],
     embed = [":db"],
     target_compatible_with = ["@platforms//cpu:x86_64"],
     deps = ["@com_github_davecgh_go_spew//spew"],
diff --git a/scouting/db/db.go b/scouting/db/db.go
index 1a43634..578edb6 100644
--- a/scouting/db/db.go
+++ b/scouting/db/db.go
@@ -1,6 +1,7 @@
 package db
 
 import (
+	"crypto/sha256"
 	"errors"
 	"fmt"
 	"gorm.io/driver/postgres"
@@ -27,6 +28,19 @@
 	R1scouter, R2scouter, R3scouter, B1scouter, B2scouter, B3scouter string
 }
 
+type PitImage struct {
+	TeamNumber string `gorm:"primaryKey"`
+	CheckSum   string `gorm:"primaryKey"`
+	ImagePath  string
+	ImageData  []byte
+}
+
+type RequestedPitImage struct {
+	TeamNumber string
+	CheckSum   string `gorm:"primaryKey"`
+	ImagePath  string
+}
+
 type Stats2023 struct {
 	// This is set to `true` for "pre-scouted" matches. This means that the
 	// match information is unlikely to correspond with an entry in the
@@ -125,7 +139,7 @@
 		return nil, errors.New(fmt.Sprint("Failed to connect to postgres: ", err))
 	}
 
-	err = database.AutoMigrate(&TeamMatch{}, &Shift{}, &Stats2023{}, &Action{}, &NotesData{}, &Ranking{}, &DriverRankingData{}, &ParsedDriverRankingData{})
+	err = database.AutoMigrate(&TeamMatch{}, &Shift{}, &Stats2023{}, &Action{}, &PitImage{}, &NotesData{}, &Ranking{}, &DriverRankingData{}, &ParsedDriverRankingData{})
 	if err != nil {
 		database.Delete()
 		return nil, errors.New(fmt.Sprint("Failed to create/migrate tables: ", err))
@@ -167,6 +181,11 @@
 	return result.Error
 }
 
+func (database *Database) AddPitImage(p PitImage) error {
+	result := database.Create(&p)
+	return result.Error
+}
+
 func (database *Database) AddToStats2023(s Stats2023) error {
 	if !s.PreScouting {
 		matches, err := database.QueryMatchesString(s.TeamNumber)
@@ -250,6 +269,12 @@
 	return actions, result.Error
 }
 
+func (database *Database) ReturnPitImages() ([]PitImage, error) {
+	var images []PitImage
+	result := database.Find(&images)
+	return images, result.Error
+}
+
 func (database *Database) ReturnStats2023() ([]Stats2023, error) {
 	var stats2023 []Stats2023
 	result := database.Find(&stats2023)
@@ -279,6 +304,28 @@
 	return matches, result.Error
 }
 
+func (database *Database) QueryPitImages(teamNumber_ string) ([]RequestedPitImage, error) {
+	var requestedPitImages []RequestedPitImage
+	result := database.Model(&PitImage{}).
+		Where("team_number = $1", teamNumber_).
+		Find(&requestedPitImages)
+
+	return requestedPitImages, result.Error
+}
+
+func (database *Database) QueryPitImageByChecksum(checksum_ string) (PitImage, error) {
+	var pitImage PitImage
+	result := database.
+		Where("check_sum = $1", checksum_).
+		Find(&pitImage)
+	return pitImage, result.Error
+}
+
+func ComputeSha256FromByteArray(arr []byte) string {
+	sum := sha256.Sum256(arr)
+	return fmt.Sprintf("%x", sum)
+}
+
 func (database *Database) QueryMatchesString(teamNumber_ string) ([]TeamMatch, error) {
 	var matches []TeamMatch
 	result := database.
diff --git a/scouting/db/db_test.go b/scouting/db/db_test.go
index d49e649..94dce7e 100644
--- a/scouting/db/db_test.go
+++ b/scouting/db/db_test.go
@@ -609,6 +609,82 @@
 	}
 }
 
+func TestQueryPitImages(t *testing.T) {
+	fixture := createDatabase(t)
+	defer fixture.TearDown()
+
+	testDatabase := []PitImage{
+		PitImage{
+			TeamNumber: "723", CheckSum: "8be8h9829hf98wp",
+			ImagePath: "image1.jpg", ImageData: []byte{14, 15, 32, 54},
+		},
+		PitImage{
+			TeamNumber: "237", CheckSum: "br78232b6r7iaa",
+			ImagePath: "bot.png", ImageData: []byte{32, 54, 23, 00},
+		},
+		PitImage{
+			TeamNumber: "125A", CheckSum: "b63c728bqiq8a73",
+			ImagePath: "file123.jpeg", ImageData: []byte{32, 05, 01, 28},
+		},
+	}
+
+	for i := 0; i < len(testDatabase); i++ {
+		err := fixture.db.AddPitImage(testDatabase[i])
+		check(t, err, fmt.Sprint("Failed to add pit image", i))
+	}
+
+	correct := []RequestedPitImage{
+		RequestedPitImage{
+			TeamNumber: "723", CheckSum: "8be8h9829hf98wp",
+			ImagePath: "image1.jpg",
+		},
+	}
+
+	got, err := fixture.db.QueryPitImages("723")
+	check(t, err, "Failed to query shift for team 723")
+
+	if !reflect.DeepEqual(correct, got) {
+		t.Fatalf("Got %#v,\nbut expected %#v.", got, correct)
+	}
+}
+
+func TestQueryPitImageByChecksum(t *testing.T) {
+	fixture := createDatabase(t)
+	defer fixture.TearDown()
+
+	testDatabase := []PitImage{
+		PitImage{
+			TeamNumber: "723", CheckSum: "8be8h9829hf98wp",
+			ImagePath: "image1.jpg", ImageData: []byte{05, 32, 00, 74, 28},
+		},
+		PitImage{
+			TeamNumber: "237", CheckSum: "br78232b6r7iaa",
+			ImagePath: "bot.png", ImageData: []byte{32, 54, 23, 00},
+		},
+		PitImage{
+			TeamNumber: "125A", CheckSum: "b63c728bqiq8a73",
+			ImagePath: "file123.jpeg", ImageData: []byte{32, 05, 01, 28},
+		},
+	}
+
+	for i := 0; i < len(testDatabase); i++ {
+		err := fixture.db.AddPitImage(testDatabase[i])
+		check(t, err, fmt.Sprint("Failed to add pit image", i))
+	}
+
+	correctPitImage := PitImage{
+		TeamNumber: "125A", CheckSum: "b63c728bqiq8a73",
+		ImagePath: "file123.jpeg", ImageData: []byte{32, 05, 01, 28},
+	}
+
+	got, err := fixture.db.QueryPitImageByChecksum("b63c728bqiq8a73")
+	check(t, err, "Failed to query shift for checksum 'b63c728bqiq8a73'")
+
+	if !reflect.DeepEqual(correctPitImage, got) {
+		t.Fatalf("Got %#v,\nbut expected %#v.", got, correctPitImage)
+	}
+}
+
 func TestQueryRankingsDB(t *testing.T) {
 	fixture := createDatabase(t)
 	defer fixture.TearDown()