Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SAC-14 Update Tag Patch #35

Merged
merged 20 commits into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions backend/src/controllers/tag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package controllers

import (
"github.com/GenerateNU/sac/backend/src/models"
"github.com/GenerateNU/sac/backend/src/services"

"github.com/gofiber/fiber/v2"
)

type TagController struct {
tagService services.TagServiceInterface
}

func NewTagController(tagService services.TagServiceInterface) *TagController {
return &TagController{tagService: tagService}
}

// CreateTag godoc
//
// @Summary Creates a tag
// @Description Creates a tag
// @ID create-tag
// @Tags tag
// @Accept json
// @Produce json
// @Success 201 {object} models.Tag
// @Failure 400 {string} string "failed to process the request"
// @Failure 400 {string} string "failed to validate the data"
// @Failure 500 {string} string "failed to create tag"
// @Router /api/v1/tags/ [post]
func (t *TagController) CreateTag(c *fiber.Ctx) error {
var tagBody models.CreateTagRequestBody

if err := c.BodyParser(&tagBody); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "failed to process the request")
}

dbTag, err := t.tagService.CreateTag(tagBody)

if err != nil {
return err
}

return c.Status(fiber.StatusCreated).JSON(&dbTag)
}

// GetTag godoc
//
// @Summary Gets a tag
// @Description Returns a tag
// @ID get-tag
// @Tags tag
// @Produce json
// @Param id path int true "Tag ID"
// @Success 200 {object} models.Tag
// @Failure 400 {string} string "failed to validate id"
// @Failure 404 {string} string "faied to find tag"
// @Failure 500 {string} string "failed to retrieve tag"
// @Router /api/v1/tags/{id} [get]
func (t *TagController) GetTag(c *fiber.Ctx) error {
tag, err := t.tagService.GetTag(c.Params("id"))

if err != nil {
return err
}

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

// UpdateTag godoc
//
// @Summary Updates a tag
// @Description Updates a tag
// @ID update-tag
// @Tags tag
// @Accept json
// @Produce json
// @Param id path int true "Tag ID"
// @Success 204
// @Failure 400 {string} string "failed to process the request"
garrettladley marked this conversation as resolved.
Show resolved Hide resolved
func (t *TagController) UpdateTag(c *fiber.Ctx) error {
var tagBody models.UpdateTagRequestBody

if err := c.BodyParser(&tagBody); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "failed to process the request")
}

err := t.tagService.UpdateTag(c.Params("id"), tagBody)

if err != nil {
return err
}

return c.SendStatus(fiber.StatusNoContent)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we be returning a no content status code? I guess it's fair but giving some key back for success to the frontend might be good @alderwhiteford mr error code god whats your opinion?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From what I've seen on the semantics sending a 204 (fiber.StatusNoContent), it means "although the server successfully processes the request, there's no additional content or response payload body that needs to be returned to the client. In essence, a 204 status code conveys 'I've done what you asked, and there's no more information to give you.'" (Abstract API)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not to be the contrarian but what would we send back? What information would the frontend need on update or delete? From my perspective, just whether it happened, or not

}

// DeleteTag godoc
//
// @Summary Deletes a tag
// @Description Deletes a tag
// @ID delete-tag
// @Tags tag
// @Param id path int true "Tag ID"
// @Success 204
garrettladley marked this conversation as resolved.
Show resolved Hide resolved
// @Failure 400 {string} string "failed to validate id"
// @Failure 404 {string} string "failed to find tag"
// @Failure 500 {string} string "failed to delete tag"
// @Router /api/v1/tags/{id} [delete]
func (t *TagController) DeleteTag(c *fiber.Ctx) error {
err := t.tagService.DeleteTag(c.Params("id"))

if err != nil {
return err
}

return c.SendStatus(fiber.StatusNoContent)
garrettladley marked this conversation as resolved.
Show resolved Hide resolved
}
20 changes: 11 additions & 9 deletions backend/src/database/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,6 @@ func MigrateDB(settings config.Settings, db *gorm.DB) error {
return err
}

tx := db.Begin()

if err := tx.Error; err != nil {
return err
}

superUser := models.User{
Role: models.Super,
NUID: "000000000",
Expand All @@ -74,7 +68,13 @@ func MigrateDB(settings config.Settings, db *gorm.DB) error {

var user models.User

if err := tx.Where("nuid = ?", superUser.NUID).First(&user).Error; err != nil {
if err := db.Where("nuid = ?", superUser.NUID).First(&user).Error; err != nil {
tx := db.Begin()

if err := tx.Error; err != nil {
return err
}

if err := tx.Create(&superUser).Error; err != nil {
tx.Rollback()
return err
Expand All @@ -99,7 +99,9 @@ func MigrateDB(settings config.Settings, db *gorm.DB) error {
tx.Rollback()
return err
}
}

return tx.Commit().Error
return tx.Commit().Error

}
return nil
}
15 changes: 14 additions & 1 deletion backend/src/models/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,22 @@ type Tag struct {

Name string `gorm:"type:varchar(255)" json:"name" validate:"required,max=255"`

CategoryID uint `gorm:"foreignKey:CategoryID" json:"category_id" validate:"-"`
CategoryID uint `gorm:"foreignKey:CategoryID" json:"category_id" validate:"required,min=1"`

User []User `gorm:"many2many:user_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"`
Club []Club `gorm:"many2many:club_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"`
Event []Event `gorm:"many2many:event_tags;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"`
}

type PartialTag struct {
Name string `json:"name" validate:"required,max=255"`
CategoryID uint `json:"category_id" validate:"required,min=1"`
}

type CreateTagRequestBody struct {
PartialTag
}

type UpdateTagRequestBody struct {
PartialTag
}
12 changes: 12 additions & 0 deletions backend/src/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func Init(db *gorm.DB) *fiber.App {

userRoutes(apiv1, &services.UserService{DB: db})
categoryRoutes(apiv1, &services.CategoryService{DB: db})
tagRoutes(apiv1, &services.TagService{DB: db})

return app
}
Expand Down Expand Up @@ -73,3 +74,14 @@ func categoryRoutes(router fiber.Router, categoryService services.CategoryServic

categories.Post("/", categoryController.CreateCategory)
}

func tagRoutes(router fiber.Router, tagService services.TagServiceInterface) {
tagController := controllers.NewTagController(tagService)

tags := router.Group("/tags")

tags.Post("/", tagController.CreateTag)
tags.Get("/:id", tagController.GetTag)
tags.Patch("/:id", tagController.UpdateTag)
tags.Delete("/:id", tagController.DeleteTag)
}
72 changes: 72 additions & 0 deletions backend/src/services/tag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package services

import (
"github.com/GenerateNU/sac/backend/src/models"
"github.com/GenerateNU/sac/backend/src/transactions"
"github.com/GenerateNU/sac/backend/src/utilities"
"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
)

type TagServiceInterface interface {
CreateTag(partialTag models.CreateTagRequestBody) (*models.Tag, error)
garrettladley marked this conversation as resolved.
Show resolved Hide resolved
GetTag(id string) (*models.Tag, error)
UpdateTag(id string, partialTag models.UpdateTagRequestBody) error
garrettladley marked this conversation as resolved.
Show resolved Hide resolved
DeleteTag(id string) error
}

type TagService struct {
DB *gorm.DB
}

func (t *TagService) CreateTag(partialTag models.CreateTagRequestBody) (*models.Tag, error) {
garrettladley marked this conversation as resolved.
Show resolved Hide resolved
tag := models.Tag{
Name: partialTag.Name,
CategoryID: partialTag.CategoryID,
}

if err := utilities.ValidateData(tag); err != nil {
return nil, fiber.NewError(fiber.StatusBadRequest, "failed to validate the data")
}

return transactions.CreateTag(t.DB, tag)
}

func (t *TagService) GetTag(id string) (*models.Tag, error) {
idAsUint, err := utilities.ValidateID(id)

if err != nil {
return nil, err
}

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

func (t *TagService) UpdateTag(id string, partialTag models.UpdateTagRequestBody) error {
idAsUint, err := utilities.ValidateID(id)

if err != nil {
return err
}

tag := models.Tag{
Name: partialTag.Name,
CategoryID: partialTag.CategoryID,
}

if err := utilities.ValidateData(tag); err != nil {
return fiber.NewError(fiber.StatusBadRequest, "failed to validate the data")
}

return transactions.UpdateTag(t.DB, *idAsUint, tag)
}

func (t *TagService) DeleteTag(id string) error {
idAsUint, err := utilities.ValidateID(id)

if err != nil {
return err
}

return transactions.DeleteTag(t.DB, *idAsUint)
}
55 changes: 55 additions & 0 deletions backend/src/transactions/tag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package transactions

import (
"errors"

"github.com/GenerateNU/sac/backend/src/models"

"github.com/gofiber/fiber/v2"
"gorm.io/gorm"
)

func CreateTag(db *gorm.DB, tag models.Tag) (*models.Tag, error) {
if err := db.Create(&tag).Error; err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, "failed to create tag")
}
return &tag, nil
}

func GetTag(db *gorm.DB, id uint) (*models.Tag, error) {
var tag models.Tag

if err := db.First(&tag, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fiber.NewError(fiber.StatusNotFound, "failed to find tag")
} else {
return nil, fiber.NewError(fiber.StatusInternalServerError, "failed to retrieve tag")
}
}

return &tag, nil
}

func UpdateTag(db *gorm.DB, id uint, tag models.Tag) error {
if err := db.Model(&models.Tag{}).Where("id = ?", id).Updates(tag).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, "failed to find tag")
} else {
return fiber.NewError(fiber.StatusInternalServerError, "failed to update tag")
}
}

return nil
}

func DeleteTag(db *gorm.DB, id uint) error {
if result := db.Delete(&models.Tag{}, id); result.RowsAffected == 0 {
if result.Error != nil {
return fiber.NewError(fiber.StatusInternalServerError, "failed to delete tag")
} else {
return fiber.NewError(fiber.StatusNotFound, "failed to find tag")
}
}

return nil
}
21 changes: 21 additions & 0 deletions backend/src/utilities/validator.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package utilities

import (
"strconv"

"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v2"
)

// Validate the data sent to the server if the data is invalid, return an error otherwise, return nil
Expand All @@ -13,3 +16,21 @@ func ValidateData(model interface{}) error {

return nil
}

garrettladley marked this conversation as resolved.
Show resolved Hide resolved
func ValidateID(id string) (*uint, error) {
idAsInt, err := strconv.Atoi(id)

errMsg := "failed to validate id"

if err != nil {
return nil, fiber.NewError(fiber.StatusBadRequest, errMsg)
}

if idAsInt < 1 { // postgres ids start at 1
return nil, fiber.NewError(fiber.StatusBadRequest, errMsg)
}

idAsUint := uint(idAsInt)

return &idAsUint, nil
}
Loading
Loading