diff --git a/Makefile b/Makefile index 0861b85..1007ebf 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ test: go test -covermode=atomic -coverprofile=coverage.out -race ./... api-doc: - swag init + swag init --tags \!Internal build: test go build diff --git a/go.mod b/go.mod index 94e4d2b..a5ef337 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/go-openapi/jsonpointer v0.20.2 // indirect github.com/go-openapi/jsonreference v0.20.4 // indirect github.com/go-openapi/spec v0.20.14 // indirect - github.com/go-openapi/swag v0.22.7 // indirect + github.com/go-openapi/swag v0.22.9 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect golang.org/x/tools v0.17.0 // indirect diff --git a/go.sum b/go.sum index 9afecd6..f2b1a33 100644 --- a/go.sum +++ b/go.sum @@ -42,8 +42,8 @@ github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdX github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= github.com/go-openapi/spec v0.20.14 h1:7CBlRnw+mtjFGlPDRZmAMnq35cRzI91xj03HVyUi/Do= github.com/go-openapi/spec v0.20.14/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw= -github.com/go-openapi/swag v0.22.7 h1:JWrc1uc/P9cSomxfnsFSVWoE1FW6bNbrVPmpQYpCcR8= -github.com/go-openapi/swag v0.22.7/go.mod h1:Gl91UqO+btAM0plGGxHqJcQZ1ZTy6jbmridBTsDy8A0= +github.com/go-openapi/swag v0.22.9 h1:XX2DssF+mQKM2DHsbgZK74y/zj4mo9I99+89xUmuZCE= +github.com/go-openapi/swag v0.22.9/go.mod h1:3/OXnFfnMAwBD099SwYRk7GD3xOrr1iL7d/XNLXVVwE= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= diff --git a/main.go b/main.go index af09418..c21ba37 100644 --- a/main.go +++ b/main.go @@ -61,6 +61,7 @@ func main() { public.POST("/login", accounts.Login) public.GET("/confirmemail", accounts.ConfirmEmail) public.POST("/forgotpassword", accounts.ForgotPassword) + public.GET("/sharedlist/:sharing_code", packs.SharedList) protected := router.Group("/api/v1") protected.Use(security.JwtAuthProcessor()) @@ -81,7 +82,6 @@ func main() { protected.POST("/myinventory", inventories.PostMyInventory) protected.PUT("/myinventory/:id", inventories.PutMyInventoryByID) protected.DELETE("/myinventory/:id", inventories.DeleteMyInventoryByID) - protected.POST("/importfromlighterpack", packs.ImportFromLighterPack) private := router.Group("/api/admin") diff --git a/pkg/database/migration/migration_scripts/000006_list_sharing_code.down.sql b/pkg/database/migration/migration_scripts/000006_list_sharing_code.down.sql new file mode 100644 index 0000000..1bab324 --- /dev/null +++ b/pkg/database/migration/migration_scripts/000006_list_sharing_code.down.sql @@ -0,0 +1,2 @@ +ALTER TABLE "pack" +DROP COLUMN "sharing_code"; \ No newline at end of file diff --git a/pkg/database/migration/migration_scripts/000006_list_sharing_code.up.sql b/pkg/database/migration/migration_scripts/000006_list_sharing_code.up.sql new file mode 100644 index 0000000..c579ebc --- /dev/null +++ b/pkg/database/migration/migration_scripts/000006_list_sharing_code.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE "pack" +ADD COLUMN "sharing_code" TEXT NOT NULL DEFAULT '', +ADD CONSTRAINT "sharing_code_unique" UNIQUE ("sharing_code"); \ No newline at end of file diff --git a/pkg/dataset/dataset.go b/pkg/dataset/dataset.go index c1d915b..df12a14 100644 --- a/pkg/dataset/dataset.go +++ b/pkg/dataset/dataset.go @@ -40,6 +40,7 @@ type Pack struct { User_id uint `json:"user_id"` Pack_name string `json:"pack_name"` Pack_description string `json:"pack_description"` + Sharing_code string `json:"sharing_code"` Created_at time.Time `json:"created_at"` Updated_at time.Time `json:"updated_at"` } diff --git a/pkg/packs/packs.go b/pkg/packs/packs.go index 79fde8b..81d1589 100644 --- a/pkg/packs/packs.go +++ b/pkg/packs/packs.go @@ -45,7 +45,7 @@ func GetPacks(c *gin.Context) { func returnPacks() (dataset.Packs, error) { var packs dataset.Packs - rows, err := database.Db().Query("SELECT id, user_id, pack_name, pack_description, created_at, updated_at FROM pack;") + rows, err := database.Db().Query("SELECT id, user_id, pack_name, pack_description, sharing_code, created_at, updated_at FROM pack;") if err != nil { return nil, err } @@ -60,7 +60,7 @@ func returnPacks() (dataset.Packs, error) { for rows.Next() { var pack dataset.Pack - err := rows.Scan(&pack.ID, &pack.User_id, &pack.Pack_name, &pack.Pack_description, &pack.Created_at, &pack.Updated_at) + err := rows.Scan(&pack.ID, &pack.User_id, &pack.Pack_name, &pack.Pack_description, &pack.Sharing_code, &pack.Created_at, &pack.Updated_at) if err != nil { return nil, err } @@ -111,8 +111,8 @@ func GetPackByID(c *gin.Context) { } -// Get pack by ID -// @Summary Get pack by ID +// Get My pack by ID +// @Summary Get My pack by ID // @Description Get pack by ID // @Security Bearer // @Tags Packs @@ -167,8 +167,8 @@ func GetMyPackByID(c *gin.Context) { func findPackById(id uint) (*dataset.Pack, error) { var pack dataset.Pack - row := database.Db().QueryRow("SELECT id, user_id, pack_name, pack_description, created_at, updated_at FROM pack WHERE id = $1;", id) - err := row.Scan(&pack.ID, &pack.User_id, &pack.Pack_name, &pack.Pack_description, &pack.Created_at, &pack.Updated_at) + row := database.Db().QueryRow("SELECT id, user_id, pack_name, pack_description, sharing_code, created_at, updated_at FROM pack WHERE id = $1;", id) + err := row.Scan(&pack.ID, &pack.User_id, &pack.Pack_name, &pack.Pack_description, &pack.Sharing_code, &pack.Created_at, &pack.Updated_at) if err != nil { if errors.Is(err, sql.ErrNoRows) { @@ -250,13 +250,18 @@ func PostMyPack(c *gin.Context) { } func insertPack(p *dataset.Pack) error { + var err error if p == nil { return errors.New("payload is empty") } p.Created_at = time.Now().Truncate(time.Second) p.Updated_at = time.Now().Truncate(time.Second) + p.Sharing_code, err = helper.GenerateRandomCode(30) + if err != nil { + return errors.New("failed to generate a sharing code") + } - err := database.Db().QueryRow("INSERT INTO pack (user_id, pack_name, pack_description, created_at, updated_at) VALUES ($1,$2,$3,$4,$5) RETURNING id;", p.User_id, p.Pack_name, p.Pack_description, p.Created_at, p.Updated_at).Scan(&p.ID) + err = database.Db().QueryRow("INSERT INTO pack (user_id, pack_name, pack_description, sharing_code, created_at, updated_at) VALUES ($1,$2,$3,$4,$5,$6) RETURNING id;", p.User_id, p.Pack_name, p.Pack_description, p.Sharing_code, p.Created_at, p.Updated_at).Scan(&p.ID) if err != nil { return err } @@ -1027,7 +1032,7 @@ func GetMyPacks(c *gin.Context) { func findPacksByUserId(id uint) (*dataset.Packs, error) { var packs dataset.Packs - rows, err := database.Db().Query("SELECT id, user_id, pack_name, pack_description, created_at, updated_at FROM pack WHERE user_id = $1;", id) + rows, err := database.Db().Query("SELECT id, user_id, pack_name, pack_description, sharing_code, created_at, updated_at FROM pack WHERE user_id = $1;", id) if err != nil { return nil, err } @@ -1035,7 +1040,7 @@ func findPacksByUserId(id uint) (*dataset.Packs, error) { for rows.Next() { var pack dataset.Pack - err := rows.Scan(&pack.ID, &pack.User_id, &pack.Pack_name, &pack.Pack_description, &pack.Created_at, &pack.Updated_at) + err := rows.Scan(&pack.ID, &pack.User_id, &pack.Pack_name, &pack.Pack_description, &pack.Sharing_code, &pack.Created_at, &pack.Updated_at) if err != nil { return nil, err } @@ -1172,7 +1177,7 @@ func ImportFromLighterPack(c *gin.Context) { lighterPack = append(lighterPack, lighterPackItem) } - // Perform your database insertion + // Perform database insertion err = insertLighterPack(&lighterPack, user_id) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) @@ -1225,3 +1230,52 @@ func insertLighterPack(lp *dataset.LighterPack, user_id uint) error { } return nil } + +// Get pack content for a given sharing code +// @Summary Get pack content for a given sharing code +// @Description Get pack content for a given sharing code +// @Tags Public +// @Produce json +// @Param sharing_code path string true "Sharing Code" +// @Success 200 {object} dataset.PackContents "Pack Contents" +// @Failure 404 {object} dataset.ErrorResponse "Pack not found" +// @Failure 500 {object} dataset.ErrorResponse "Internal Server Error" +// @Router /public/packs/{sharing_code} [get] +func SharedList(c *gin.Context) { + sharing_code := c.Param("sharing_code") + + pack_id, err := findPackIdBySharingCode(sharing_code) + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + if pack_id == 0 { + c.IndentedJSON(http.StatusNotFound, gin.H{"error": "Pack not found"}) + return + } + + packContents, err := returnPackContentsByPackID(pack_id) + if err != nil { + c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + if packContents == nil { + c.IndentedJSON(http.StatusNotFound, gin.H{"error": "Pack not found"}) + return + } + + c.IndentedJSON(http.StatusOK, packContents) +} + +func findPackIdBySharingCode(sharing_code string) (uint, error) { + var pack_id uint + row := database.Db().QueryRow("SELECT id FROM pack WHERE sharing_code = $1;", sharing_code) + err := row.Scan(&pack_id) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return 0, nil + } + return 0, err + } + return pack_id, nil +} diff --git a/pkg/packs/packs_test.go b/pkg/packs/packs_test.go index 2e3ec42..7e44d63 100644 --- a/pkg/packs/packs_test.go +++ b/pkg/packs/packs_test.go @@ -11,6 +11,7 @@ import ( "net/http" "net/http/httptest" "os" + "reflect" "testing" "github.com/Angak0k/pimpmypack/pkg/config" @@ -106,18 +107,24 @@ func TestGetPacks(t *testing.T) { t.Errorf("Expected Pack Name %v but got %v", packs[0].Pack_name, getPacks[0].Pack_name) case !cmp.Equal(getPacks[0].Pack_description, packs[0].Pack_description): t.Errorf("Expected Pack Description %v but got %v", packs[0].Pack_description, getPacks[0].Pack_description) + case !cmp.Equal(getPacks[0].Sharing_code, packs[0].Sharing_code): + t.Errorf("Expected Sharing Code %v but got %v", packs[0].Sharing_code, getPacks[0].Sharing_code) case !cmp.Equal(getPacks[1].User_id, packs[1].User_id): t.Errorf("Expected User ID %v but got %v", packs[1].User_id, getPacks[1].User_id) case !cmp.Equal(getPacks[1].Pack_name, packs[1].Pack_name): t.Errorf("Expected Pack Name %v but got %v", packs[1].Pack_name, getPacks[1].Pack_name) case !cmp.Equal(getPacks[1].Pack_description, packs[1].Pack_description): t.Errorf("Expected Pack Description %v but got %v", packs[1].Pack_description, getPacks[1].Pack_description) + case !cmp.Equal(getPacks[1].Sharing_code, packs[1].Sharing_code): + t.Errorf("Expected Sharing Code %v but got %v", packs[1].Sharing_code, getPacks[1].Sharing_code) case !cmp.Equal(getPacks[2].User_id, packs[2].User_id): t.Errorf("Expected User ID %v but got %v", packs[2].User_id, getPacks[2].User_id) case !cmp.Equal(getPacks[2].Pack_name, packs[2].Pack_name): t.Errorf("Expected Pack Name %v but got %v", packs[2].Pack_name, getPacks[2].Pack_name) case !cmp.Equal(getPacks[2].Pack_description, packs[2].Pack_description): t.Errorf("Expected Pack Description %v but got %v", packs[2].Pack_description, getPacks[2].Pack_description) + case !cmp.Equal(getPacks[2].Sharing_code, packs[2].Sharing_code): + t.Errorf("Expected Sharing Code %v but got %v", packs[2].Sharing_code, getPacks[2].Sharing_code) } } }) @@ -162,6 +169,8 @@ func TestGetPackByID(t *testing.T) { t.Errorf("Expected Pack Name %v but got %v", packs[0].Pack_name, receivedPack.Pack_name) case receivedPack.Pack_description != packs[0].Pack_description: t.Errorf("Expected Pack Description %v but got %v", packs[0].Pack_description, receivedPack.Pack_description) + case receivedPack.Sharing_code != packs[0].Sharing_code: + t.Errorf("Expected Sharing Code %v but got %v", packs[0].Sharing_code, receivedPack.Sharing_code) } }) @@ -223,8 +232,8 @@ func TestPostPack(t *testing.T) { // Query the database to get the inserted pack var insertedPack dataset.Pack - row := database.Db().QueryRow("SELECT * FROM pack WHERE pack_name = $1;", newPack.Pack_name) - err = row.Scan(&insertedPack.ID, &insertedPack.User_id, &insertedPack.Pack_name, &insertedPack.Pack_description, &insertedPack.Created_at, &insertedPack.Updated_at) + row := database.Db().QueryRow("SELECT id, user_id, pack_name, pack_description, sharing_code, created_at, updated_at FROM pack WHERE pack_name = $1;", newPack.Pack_name) + err = row.Scan(&insertedPack.ID, &insertedPack.User_id, &insertedPack.Pack_name, &insertedPack.Pack_description, &insertedPack.Sharing_code, &insertedPack.Created_at, &insertedPack.Updated_at) if err != nil { if errors.Is(err, sql.ErrNoRows) { fmt.Println("No rows were returned!") @@ -246,6 +255,8 @@ func TestPostPack(t *testing.T) { t.Errorf("Expected Pack Name %v but got %v", insertedPack.Pack_name, receivedPack.Pack_name) case receivedPack.Pack_description != insertedPack.Pack_description: t.Errorf("Expected Pack Description %v but got %v", insertedPack.Pack_description, receivedPack.Pack_description) + case receivedPack.Sharing_code == "": + t.Errorf("Expected a non empty Sharing Code but got %v", receivedPack.Sharing_code) } }) } @@ -293,8 +304,8 @@ func TestPutPackByID(t *testing.T) { // Query the database to get the updated pack var updatedPack dataset.Pack - row := database.Db().QueryRow("SELECT * FROM pack WHERE id = $1;", packs[2].ID) - err = row.Scan(&updatedPack.ID, &updatedPack.User_id, &updatedPack.Pack_name, &updatedPack.Pack_description, &updatedPack.Created_at, &updatedPack.Updated_at) + row := database.Db().QueryRow("SELECT id, user_id, pack_name, pack_description, sharing_code, created_at, updated_at FROM pack WHERE id = $1;", packs[2].ID) + err = row.Scan(&updatedPack.ID, &updatedPack.User_id, &updatedPack.Pack_name, &updatedPack.Pack_description, &updatedPack.Sharing_code, &updatedPack.Created_at, &updatedPack.Updated_at) if err != nil { if errors.Is(err, sql.ErrNoRows) { fmt.Println("No rows were returned!") @@ -839,3 +850,135 @@ item2,category2,description2,2,150,g,http://example2.com,20,,consumable` }) } } + +func TestCheckPackOwnership(t *testing.T) { + testCases := []struct { + packID uint + userID uint + expected bool + name string + }{ + { + packID: packs[3].ID, + userID: packs[3].User_id, + expected: true, + name: "Owner checks their own pack", + }, + { + packID: packs[2].ID, + userID: packs[3].User_id, + expected: false, + name: "Non-owner checks someone else's pack", + }, + } + + // Loop through each test case + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Call the function under test + test, err := checkPackOwnership(tc.packID, tc.userID) + if err != nil { + t.Fatalf("Failed to check pack ownership: %v", err) + } + if test != tc.expected { + t.Errorf("Expected %v but got %v", tc.expected, test) + } + }) + } +} + +func TestFindPackIdBySharingCode(t *testing.T) { + testCases := []struct { + sharingCode string + expected uint + name string + }{ + { + sharingCode: packs[3].Sharing_code, + expected: packs[3].ID, + name: "Valid sharing code", + }, + { + sharingCode: "invalid", + expected: 0, + name: "Invalid sharing code", + }, + } + + // Loop through each test case + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Call the function under test + test, err := findPackIdBySharingCode(tc.sharingCode) + if err != nil { + t.Fatalf("Failed to find pack ID by sharing code: %v", err) + } + if test != tc.expected { + t.Errorf("Expected %v but got %v", tc.expected, test) + } + }) + } +} + +func TestGetPackBySharingCode(t *testing.T) { + testCases := []struct { + sharingCode string + responseCode int + expected dataset.PackContents + name string + }{ + { + sharingCode: packs[0].Sharing_code, + responseCode: http.StatusOK, + expected: packItems[0:1], + name: "Valid sharing code", + }, + { + sharingCode: "invalid", + responseCode: http.StatusNotFound, + expected: dataset.PackContents{}, + name: "Invalid sharing code", + }, + } + // Set Gin to test mode + gin.SetMode(gin.TestMode) + + // Create a Gin router instance + router := gin.Default() + + // Define the endpoint for DeletePackByID handler + router.GET("/sharedlist/:sharing_code", SharedList) + + // Loop through each test case + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + var returnedPackContents dataset.PackContents + // Set up a test scenario: sending a GET request + path := fmt.Sprintf("/sharedlist/%s", tc.sharingCode) + req, err := http.NewRequest("GET", path, nil) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + req.Header.Set("Content-Type", "application/json") + + w := httptest.NewRecorder() + router.ServeHTTP(w, req) + + if w.Code != tc.responseCode { + t.Errorf("Expected status code %d but got %d", tc.responseCode, w.Code) + } + + if tc.responseCode == http.StatusOK { + // Unmarshal the response body into a slice of packs struct + if err := json.Unmarshal(w.Body.Bytes(), &returnedPackContents); err != nil { + t.Fatalf("Failed to unmarshal response body: %v", err) + } + + // determine if the answer is correct + if reflect.DeepEqual(returnedPackContents, tc.expected) { + t.Errorf("Expected %v but got %v", tc.expected, returnedPackContents) + } + } + }) + } +} diff --git a/pkg/packs/testdata.go b/pkg/packs/testdata.go index 797ccdf..2b6cb00 100644 --- a/pkg/packs/testdata.go +++ b/pkg/packs/testdata.go @@ -25,7 +25,7 @@ var users = []dataset.User{ { Username: fmt.Sprintf("user-%s", random.UniqueId()), Email: fmt.Sprintf("user-%s@exemple.com", random.UniqueId()), - Firstname: "John", + Firstname: "Jane", Lastname: "Doe", Role: "standard", Status: "active", @@ -75,21 +75,25 @@ var packs = dataset.Packs{ User_id: 1, Pack_name: "First Pack", Pack_description: "Description for the first pack", + Sharing_code: "123456", }, { User_id: 1, Pack_name: "Second Pack", Pack_description: "Description for the second pack", + Sharing_code: "654321", }, { User_id: 2, Pack_name: "Third Pack", Pack_description: "Description for the third pack", + Sharing_code: "789456", }, { User_id: 1, Pack_name: "Special Pack", Pack_description: "Description for the second pack", + Sharing_code: "321654", }, } @@ -224,8 +228,8 @@ func loadingPackDataset() error { // Insert packs dataset for i := range packs { - err := database.Db().QueryRow("INSERT INTO pack (user_id, pack_name, pack_description, created_at, updated_at) VALUES ($1,$2,$3,$4,$5) RETURNING id;", - packs[i].User_id, packs[i].Pack_name, packs[i].Pack_description, time.Now().Truncate(time.Second), time.Now().Truncate(time.Second)).Scan(&packs[i].ID) + err := database.Db().QueryRow("INSERT INTO pack (user_id, pack_name, pack_description, sharing_code, created_at, updated_at) VALUES ($1,$2,$3,$4,$5,$6) RETURNING id;", + packs[i].User_id, packs[i].Pack_name, packs[i].Pack_description, packs[i].Sharing_code, time.Now().Truncate(time.Second), time.Now().Truncate(time.Second)).Scan(&packs[i].ID) if err != nil { return err }