Skip to content

Commit

Permalink
SAC-12 Category RUD (#41)
Browse files Browse the repository at this point in the history
Co-authored-by: Alder Whiteford <[email protected]>
Co-authored-by: Garrett Ladley <[email protected]>
Co-authored-by: garrettladley <[email protected]>
  • Loading branch information
4 people authored Jan 24, 2024
1 parent 736f4f1 commit a8320a4
Show file tree
Hide file tree
Showing 17 changed files with 453 additions and 66 deletions.
95 changes: 94 additions & 1 deletion backend/src/controllers/category.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package controllers

import (
"strconv"

"github.com/GenerateNU/sac/backend/src/errors"
"github.com/GenerateNU/sac/backend/src/models"
"github.com/GenerateNU/sac/backend/src/services"
Expand Down Expand Up @@ -33,7 +35,7 @@ func (t *CategoryController) CreateCategory(c *fiber.Ctx) error {
var categoryBody models.CategoryRequestBody

if err := c.BodyParser(&categoryBody); err != nil {
return errors.FailedToValidateCategory.FiberError(c)
return errors.FailedToParseRequestBody.FiberError(c)
}

newCategory, err := t.categoryService.CreateCategory(categoryBody)
Expand All @@ -44,3 +46,94 @@ func (t *CategoryController) CreateCategory(c *fiber.Ctx) error {

return c.Status(fiber.StatusCreated).JSON(newCategory)
}

// GetCategories godoc
//
// @Summary Retrieve all categories
// @Description Retrieves all existing categories
// @ID get-categories
// @Tags category
// @Produce json
// @Success 200 {object} []models.Category
// @Failure 500 {string} string "unable to retrieve categories"
// @Router /api/v1/category/ [get]
func (t *CategoryController) GetCategories(c *fiber.Ctx) error {
defaultLimit := 10
defaultPage := 1

categories, err := t.categoryService.GetCategories(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(&categories)
}

// GetCategory godoc
//
// @Summary Retrieve a category
// @Description Retrieve a category by its ID
// @ID get-category
// @Tags category
// @Produce json
// @Success 200 {object} models.Category
// @Failure 400 {string} string "failed to validate id"
// @Failure 404 {string} string "faied to find category"
// @Failure 500 {string} string "failed to retrieve category"
// @Router /api/v1/category/{id} [get]
func (t *CategoryController) GetCategory(c *fiber.Ctx) error {
category, err := t.categoryService.GetCategory(c.Params("id"))
if err != nil {
return err.FiberError(c)
}

return c.Status(fiber.StatusOK).JSON(&category)
}

// DeleteCategory godoc
//
// @Summary Delete a category
// @Description Delete a category by ID
// @ID delete-category
// @Tags category
// @Produce json
// @Success 204 {object}
// @Failure 400 {string} string "failed to validate id"
// @Failure 404 {string} string "failed to find category"
// @Failure 500 {string} string "failed to delete category"
// @Router /api/v1/category/{id} [delete]
func (t *CategoryController) DeleteCategory(c *fiber.Ctx) error {
if err := t.categoryService.DeleteCategory(c.Params("id")); err != nil {
return err.FiberError(c)
}

return c.SendStatus(fiber.StatusNoContent)
}

// UpdateCategory godoc
//
// @Summary Updates a category
// @Description Updates a category
// @ID update-category
// @Tags category
// @Produce json
// @Success 200 {object} models.Category
// @Failure 400 {string} string "failed to validate id"
// @Failure 404 {string} string "failed to find category"
// @Failure 500 {string} string "failed to update category"
// @Router /api/v1/category/{id} [patch]
func (t *CategoryController) UpdateCategory(c *fiber.Ctx) error {
var category models.CategoryRequestBody

if err := c.BodyParser(&category); err != nil {
return errors.FailedToValidateCategory.FiberError(c)
}

updatedCategory, err := t.categoryService.UpdateCategory(c.Params("id"), category)

if err != nil {
return err.FiberError(c)
}

return c.Status(fiber.StatusOK).JSON(updatedCategory)
}
12 changes: 12 additions & 0 deletions backend/src/errors/category.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ var (
StatusCode: fiber.StatusInternalServerError,
Message: "failed to get category",
}
FailedToGetCategories = Error{
StatusCode: fiber.StatusInternalServerError,
Message: "failed to get categories",
}
FailedToUpdateCategory = Error{
StatusCode: fiber.StatusInternalServerError,
Message: "failed to update category",
}
FailedToDeleteCategory = Error{
StatusCode: fiber.StatusInternalServerError,
Message: "failed to delete category",
}
CategoryAlreadyExists = Error{
StatusCode: fiber.StatusConflict,
Message: "category already exists",
Expand Down
16 changes: 14 additions & 2 deletions backend/src/errors/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,24 @@ var (
StatusCode: fiber.StatusBadRequest,
Message: "failed to validate id",
}
FailedToMapResposeToModel = Error{
FailedToValidateNonNegativeValue = Error{
StatusCode: fiber.StatusBadRequest,
Message: "failed to validate non-negative value",
}
FailedToMapRequestToModel = Error{
StatusCode: fiber.StatusInternalServerError,
Message: "failed to map response to model",
Message: "failed to map request to model",
}
InternalServerError = Error{
StatusCode: fiber.StatusInternalServerError,
Message: "internal server error",
}
FailedToValidateLimit = Error{
StatusCode: fiber.StatusBadRequest,
Message: "failed to validate limit",
}
FailedToValidatePage = Error{
StatusCode: fiber.StatusBadRequest,
Message: "failed to validate page",
}
)
4 changes: 2 additions & 2 deletions backend/src/models/category.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import "github.com/GenerateNU/sac/backend/src/types"
type Category struct {
types.Model

Name string `gorm:"type:varchar(255);unique" json:"category_name" validate:"required,max=255"`
Name string `gorm:"type:varchar(255);unique" json:"name" validate:"required,max=255"`
Tag []Tag `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"`
}

type CategoryRequestBody struct {
Name string `json:"category_name" validate:"required,max=255"`
Name string `json:"name" validate:"required,max=255"`
}
4 changes: 4 additions & 0 deletions backend/src/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ func categoryRoutes(router fiber.Router, categoryService services.CategoryServic
categories := router.Group("/categories")

categories.Post("/", categoryController.CreateCategory)
categories.Get("/", categoryController.GetCategories)
categories.Get("/:id", categoryController.GetCategory)
categories.Delete("/:id", categoryController.DeleteCategory)
categories.Patch("/:id", categoryController.UpdateCategory)
}

func tagRoutes(router fiber.Router, tagService services.TagServiceInterface) {
Expand Down
65 changes: 63 additions & 2 deletions backend/src/services/category.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import (

type CategoryServiceInterface interface {
CreateCategory(categoryBody models.CategoryRequestBody) (*models.Category, *errors.Error)
GetCategory(id string) (*models.Category, *errors.Error)
GetCategories(limit string, page string) ([]models.Category, *errors.Error)
UpdateCategory(id string, params models.CategoryRequestBody) (*models.Category, *errors.Error)
DeleteCategory(id string) *errors.Error
}

type CategoryService struct {
Expand All @@ -28,12 +32,69 @@ func (c *CategoryService) CreateCategory(categoryBody models.CategoryRequestBody
return nil, &errors.FailedToValidateCategory
}

category, err := utilities.MapResponseToModel(categoryBody, &models.Category{})
category, err := utilities.MapRequestToModel(categoryBody, &models.Category{})
if err != nil {
return nil, &errors.FailedToMapResposeToModel
return nil, &errors.FailedToMapRequestToModel
}

category.Name = cases.Title(language.English).String(category.Name)

return transactions.CreateCategory(c.DB, *category)
}

func (c *CategoryService) GetCategories(limit string, page string) ([]models.Category, *errors.Error) {
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.GetCategories(c.DB, *limitAsInt, offset)
}

func (c *CategoryService) GetCategory(id string) (*models.Category, *errors.Error) {
uintId, err := utilities.ValidateID(id)

if err != nil {
return nil, err
}

return transactions.GetCategory(c.DB, *uintId)
}

func (c *CategoryService) UpdateCategory(id string, categoryBody models.CategoryRequestBody) (*models.Category, *errors.Error) {
idAsUint, idErr := utilities.ValidateID(id)
if idErr != nil {
return nil, idErr
}

if err := c.Validate.Struct(categoryBody); err != nil {
return nil, &errors.FailedToValidateTag
}

category, err := utilities.MapRequestToModel(categoryBody, &models.Category{})
if err != nil {
return nil, &errors.FailedToMapRequestToModel
}

category.Name = cases.Title(language.English).String(category.Name)

return transactions.UpdateCategory(c.DB, *idAsUint, *category)
}

func (c *CategoryService) DeleteCategory(id string) *errors.Error {
idAsUInt, err := utilities.ValidateID(id)
if err != nil {
return err
}

return transactions.DeleteCategory(c.DB, *idAsUInt)
}
16 changes: 8 additions & 8 deletions backend/src/services/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ func (t *TagService) CreateTag(tagBody models.TagRequestBody) (*models.Tag, *err
return nil, &errors.FailedToValidateTag
}

tag, err := utilities.MapResponseToModel(tagBody, &models.Tag{})
tag, err := utilities.MapRequestToModel(tagBody, &models.Tag{})
if err != nil {
return nil, &errors.FailedToMapResposeToModel
return nil, &errors.FailedToMapRequestToModel
}

return transactions.CreateTag(t.DB, *tag)
Expand All @@ -37,25 +37,25 @@ func (t *TagService) CreateTag(tagBody models.TagRequestBody) (*models.Tag, *err
func (t *TagService) GetTag(id string) (*models.Tag, *errors.Error) {
idAsUint, err := utilities.ValidateID(id)
if err != nil {
return nil, &errors.FailedToValidateID
return nil, err
}

return transactions.GetTag(t.DB, *idAsUint)
}

func (t *TagService) UpdateTag(id string, tagBody models.TagRequestBody) (*models.Tag, *errors.Error) {
idAsUint, err := utilities.ValidateID(id)
if err != nil {
return nil, &errors.FailedToValidateID
idAsUint, idErr := utilities.ValidateID(id)
if idErr != nil {
return nil, idErr
}

if err := t.Validate.Struct(tagBody); err != nil {
return nil, &errors.FailedToValidateTag
}

tag, err := utilities.MapResponseToModel(tagBody, &models.Tag{})
tag, err := utilities.MapRequestToModel(tagBody, &models.Tag{})
if err != nil {
return nil, &errors.FailedToMapResposeToModel
return nil, &errors.FailedToMapRequestToModel
}

return transactions.UpdateTag(t.DB, *idAsUint, *tag)
Expand Down
17 changes: 8 additions & 9 deletions backend/src/services/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ func (u *UserService) CreateUser(userBody models.CreateUserRequestBody) (*models
return nil, &errors.FailedToValidateUser
}

user, err := utilities.MapResponseToModel(userBody, &models.User{})
user, err := utilities.MapRequestToModel(userBody, &models.User{})
if err != nil {
return nil, &errors.FailedToMapResposeToModel
return nil, &errors.FailedToMapRequestToModel
}

passwordHash, err := auth.ComputePasswordHash(userBody.Password)
Expand All @@ -62,9 +62,9 @@ func (u *UserService) GetUser(id string) (*models.User, *errors.Error) {
}

func (u *UserService) UpdateUser(id string, userBody models.UpdateUserRequestBody) (*models.User, *errors.Error) {
idAsUint, err := utilities.ValidateID(id)
if err != nil {
return nil, &errors.FailedToValidateID
idAsUint, idErr := utilities.ValidateID(id)
if idErr != nil {
return nil, idErr
}

if err := u.Validate.Struct(userBody); err != nil {
Expand All @@ -76,21 +76,20 @@ func (u *UserService) UpdateUser(id string, userBody models.UpdateUserRequestBod
return nil, &errors.FailedToComputePasswordHash
}

user, err := utilities.MapResponseToModel(userBody, &models.User{})
user, err := utilities.MapRequestToModel(userBody, &models.User{})
if err != nil {
return nil, &errors.FailedToMapResposeToModel
return nil, &errors.FailedToMapRequestToModel
}

user.PasswordHash = *passwordHash

return transactions.UpdateUser(u.DB, *idAsUint, *user)
}

// Delete user with a specific id
func (u *UserService) DeleteUser(id string) *errors.Error {
idAsInt, err := utilities.ValidateID(id)
if err != nil {
return &errors.FailedToValidateID
return err
}

return transactions.DeleteUser(u.DB, *idAsInt)
Expand Down
34 changes: 34 additions & 0 deletions backend/src/transactions/category.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,37 @@ func GetCategory(db *gorm.DB, id uint) (*models.Category, *errors.Error) {

return &category, nil
}

func GetCategories(db *gorm.DB, limit int, offset int) ([]models.Category, *errors.Error) {
var categories []models.Category

if err := db.Limit(limit).Offset(offset).Find(&categories).Error; err != nil {
return nil, &errors.FailedToGetCategories
}

return categories, nil
}

func UpdateCategory(db *gorm.DB, id uint, category models.Category) (*models.Category, *errors.Error) {
if err := db.Model(&models.Category{}).Where("id = ?", id).Updates(category).First(&category, id).Error; err != nil {
if stdliberrors.Is(err, gorm.ErrRecordNotFound) {
return nil, &errors.TagNotFound
} else {
return nil, &errors.FailedToUpdateTag
}
}

return &category, nil
}

func DeleteCategory(db *gorm.DB, id uint) *errors.Error {
if result := db.Delete(&models.Category{}, id); result.RowsAffected == 0 {
if result.Error == nil {
return &errors.CategoryNotFound
} else {
return &errors.FailedToDeleteCategory
}
}

return nil
}
Loading

0 comments on commit a8320a4

Please sign in to comment.