diff --git a/backend/src/controllers/category_tag.go b/backend/src/controllers/category_tag.go index 2cb0d7625..0c3fa7a27 100644 --- a/backend/src/controllers/category_tag.go +++ b/backend/src/controllers/category_tag.go @@ -1,6 +1,8 @@ package controllers import ( + "strconv" + "github.com/GenerateNU/sac/backend/src/services" "github.com/gofiber/fiber/v2" @@ -14,6 +16,18 @@ func NewCategoryTagController(categoryTagService services.CategoryTagServiceInte return &CategoryTagController{categoryTagService: categoryTagService} } +func (t *CategoryTagController) GetTagsByCategory(c *fiber.Ctx) error { + defaultLimit := 10 + defaultPage := 1 + + tags, err := t.categoryTagService.GetTagsByCategory(c.Params("categoryID"), c.Query("limit", strconv.Itoa(defaultLimit)), c.Query("page", strconv.Itoa(defaultPage))) + if err != nil { + return err.FiberError(c) + } + + return c.Status(fiber.StatusOK).JSON(&tags) +} + func (t *CategoryTagController) GetTagByCategory(c *fiber.Ctx) error { tag, err := t.categoryTagService.GetTagByCategory(c.Params("categoryID"), c.Params("tagID")) if err != nil { diff --git a/backend/src/errors/tag.go b/backend/src/errors/tag.go index d872d8105..9e67a0a7c 100644 --- a/backend/src/errors/tag.go +++ b/backend/src/errors/tag.go @@ -11,6 +11,10 @@ var ( StatusCode: fiber.StatusInternalServerError, Message: "failed to create tag", } + FailedToGetTags = Error{ + StatusCode: fiber.StatusInternalServerError, + Message: "failed to get tags", + } FailedToGetTag = Error{ StatusCode: fiber.StatusInternalServerError, Message: "failed to get tag", diff --git a/backend/src/server/server.go b/backend/src/server/server.go index 8cc5ef224..3b892d65f 100644 --- a/backend/src/server/server.go +++ b/backend/src/server/server.go @@ -162,5 +162,6 @@ func categoryTagRoutes(router fiber.Router, categoryTagService services.Category categoryTags := router.Group("/:categoryID/tags") + categoryTags.Get("/", categoryTagController.GetTagsByCategory) categoryTags.Get("/:tagID", categoryTagController.GetTagByCategory) } diff --git a/backend/src/services/category_tag.go b/backend/src/services/category_tag.go index abafb622d..a16286687 100644 --- a/backend/src/services/category_tag.go +++ b/backend/src/services/category_tag.go @@ -10,6 +10,7 @@ import ( ) type CategoryTagServiceInterface interface { + GetTagsByCategory(categoryID string, limit string, page string) ([]models.Tag, *errors.Error) GetTagByCategory(categoryID string, tagID string) (*models.Tag, *errors.Error) } @@ -22,6 +23,27 @@ func NewCategoryTagService(db *gorm.DB, validate *validator.Validate) *CategoryT return &CategoryTagService{DB: db, Validate: validate} } +func (t *CategoryTagService) GetTagsByCategory(categoryID string, limit string, page string) ([]models.Tag, *errors.Error) { + categoryIDAsUUID, err := utilities.ValidateID(categoryID) + if err != nil { + return nil, err + } + + limitAsInt, err := utilities.ValidateNonNegative(limit) + if err != nil { + return nil, &errors.FailedToValidateLimit + } + + pageAsInt, err := utilities.ValidateNonNegative(page) + if err != nil { + return nil, &errors.FailedToValidatePage + } + + offset := (*pageAsInt - 1) * *limitAsInt + + return transactions.GetTagsByCategory(t.DB, *categoryIDAsUUID, *limitAsInt, offset) +} + func (t *CategoryTagService) GetTagByCategory(categoryID string, tagID string) (*models.Tag, *errors.Error) { categoryIDAsUUID, idErr := utilities.ValidateID(categoryID) diff --git a/backend/src/transactions/category_tag.go b/backend/src/transactions/category_tag.go index b5d1a3ad9..7ef3129a7 100644 --- a/backend/src/transactions/category_tag.go +++ b/backend/src/transactions/category_tag.go @@ -11,6 +11,15 @@ import ( "gorm.io/gorm" ) +func GetTagsByCategory(db *gorm.DB, categoryID uuid.UUID, limit int, offset int) ([]models.Tag, *errors.Error) { + var tags []models.Tag + if err := db.Where("category_id = ?", categoryID).Limit(limit).Offset(offset).Find(&tags).Error; err != nil { + return nil, &errors.FailedToGetTags + } + + return tags, nil +} + func GetTagByCategory(db *gorm.DB, categoryID uuid.UUID, tagID uuid.UUID) (*models.Tag, *errors.Error) { var tag models.Tag if err := db.Where("category_id = ? AND id = ?", categoryID, tagID).First(&tag).Error; err != nil { diff --git a/backend/tests/api/category_tag_test.go b/backend/tests/api/category_tag_test.go index ca8701d29..123e7bb26 100644 --- a/backend/tests/api/category_tag_test.go +++ b/backend/tests/api/category_tag_test.go @@ -1 +1,182 @@ package tests + +import ( + "fmt" + "net/http" + "testing" + + "github.com/GenerateNU/sac/backend/src/errors" + "github.com/GenerateNU/sac/backend/src/models" + "github.com/goccy/go-json" + "github.com/gofiber/fiber/v2" + "github.com/google/uuid" + "github.com/huandu/go-assert" +) + +func AssertTagsWithBodyRespDB(app TestApp, assert *assert.A, resp *http.Response, body *[]map[string]interface{}) []uuid.UUID { + var respTags []models.Tag + + err := json.NewDecoder(resp.Body).Decode(&respTags) + + assert.NilError(err) + + var dbTags []models.Tag + + err = app.Conn.Find(&dbTags).Error + + assert.NilError(err) + + assert.Equal(len(dbTags), len(respTags)) + + for i, dbTag := range dbTags { + assert.Equal(dbTag.ID, respTags[i].ID) + assert.Equal(dbTag.Name, respTags[i].Name) + assert.Equal(dbTag.CategoryID, respTags[i].CategoryID) + } + + tagIDs := make([]uuid.UUID, len(dbTags)) + for i, dbTag := range dbTags { + tagIDs[i] = dbTag.ID + } + + return tagIDs +} + +func TestGetCategoryTagsWorks(t *testing.T) { + appAssert, categoryUUID, tagID := CreateSampleTag(t) + + body := SampleTagFactory(categoryUUID) + (*body)["id"] = tagID + + TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s/tags", categoryUUID), + }.TestOnStatusAndDB(t, &appAssert, + DBTesterWithStatus{ + Status: fiber.StatusOK, + DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + AssertTagsWithBodyRespDB(app, assert, resp, &[]map[string]interface{}{*body}) + }, + }, + ).Close() +} + +func TestGetCategoryTagsFailsCategoryBadRequest(t *testing.T) { + appAssert, _ := CreateSampleCategory(t, nil) + + badRequests := []string{ + "0", + "-1", + "1.1", + "foo", + "null", + } + + for _, badRequest := range badRequests { + TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s/tags", badRequest), + }.TestOnError(t, &appAssert, errors.FailedToValidateID) + } + + appAssert.Close() +} + +func TestGetCategoryTagsFailsCategoryNotFound(t *testing.T) { + appAssert, _ := CreateSampleCategory(t, nil) + + uuid := uuid.New() + + TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s/tags", uuid), + }.TestOnErrorAndDB(t, &appAssert, ErrorWithDBTester{ + Error: errors.CategoryNotFound, + DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + var category models.Category + err := app.Conn.Where("id = ?", uuid).First(&category).Error + assert.Error(err) + + var respBody []map[string]interface{} + err = json.NewDecoder(resp.Body).Decode(&respBody) + assert.NilError(err) + assert.Equal(0, len(respBody)) + }, + }).Close() +} + +func TestGetCategoryTagWorks(t *testing.T) { + existingAppAssert, categoryUUID, tagUUID := CreateSampleTag(t) + + TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", categoryUUID, tagUUID), + }.TestOnStatusAndDB(t, &existingAppAssert, + DBTesterWithStatus{ + Status: fiber.StatusOK, + DBTester: func(app TestApp, assert *assert.A, resp *http.Response) { + AssertTagWithBodyRespDB(app, assert, resp, SampleTagFactory(categoryUUID)) + }, + }, + ).Close() +} + +func TestGetCategoryTagFailsCategoryBadRequest(t *testing.T) { + appAssert, _, tagUUID := CreateSampleTag(t) + + badRequests := []string{ + "0", + "-1", + "1.1", + "foo", + "null", + } + + for _, badRequest := range badRequests { + TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", badRequest, tagUUID), + }.TestOnError(t, &appAssert, errors.FailedToValidateID) + } + + appAssert.Close() +} + +func TestGetCategoryTagFailsTagBadRequest(t *testing.T) { + appAssert, categoryUUID := CreateSampleCategory(t, nil) + + badRequests := []string{ + "0", + "-1", + "1.1", + "foo", + "null", + } + + for _, badRequest := range badRequests { + TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", categoryUUID, badRequest), + }.TestOnError(t, &appAssert, errors.FailedToValidateID) + } + + appAssert.Close() +} + +func TestGetCategoryTagFailsCategoryNotFound(t *testing.T) { + appAssert, _, tagUUID := CreateSampleTag(t) + + TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", uuid.New(), tagUUID), + }.TestOnError(t, &appAssert, errors.TagNotFound).Close() +} + +func TestGetCategoryTagFailsTagNotFound(t *testing.T) { + appAssert, categoryUUID := CreateSampleCategory(t, nil) + + TestRequest{ + Method: fiber.MethodGet, + Path: fmt.Sprintf("/api/v1/categories/%s/tags/%s", categoryUUID, uuid.New()), + }.TestOnError(t, &appAssert, errors.TagNotFound).Close() +}