Skip to content

Commit

Permalink
SAC-13 Create Tag POST (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
garrettladley authored Jan 21, 2024
1 parent 91dea77 commit cde14a8
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 56 deletions.
45 changes: 45 additions & 0 deletions backend/src/controllers/tag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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)
}
14 changes: 11 additions & 3 deletions backend/src/database/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,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 @@ -106,7 +112,9 @@ func MigrateDB(settings config.Settings, db *gorm.DB) error {
tx.Rollback()
return err
}
}

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

}
return nil
}
7 changes: 6 additions & 1 deletion backend/src/models/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ 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"`

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 CreateTagRequestBody struct {
Name string `json:"name" validate:"required"`
CategoryID uint `json:"category_id" validate:"required"`
}
9 changes: 9 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,11 @@ 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)
}
30 changes: 30 additions & 0 deletions backend/src/services/tag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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)
}

type TagService struct {
DB *gorm.DB
}

func (t *TagService) CreateTag(partialTag models.CreateTagRequestBody) (*models.Tag, error) {
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)
}
31 changes: 31 additions & 0 deletions backend/src/transactions/tag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
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.StatusBadRequest, "invalid tag id")
} else {
return nil, fiber.NewError(fiber.StatusInternalServerError, "unable to retrieve tag")
}
}

return &tag, nil
}
61 changes: 43 additions & 18 deletions backend/tests/api/category_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func CreateSampleCategory(t *testing.T, categoryName string) ExistingAppAssert {
Body: &map[string]interface{}{
"category_name": categoryName,
},
}.TestOnStatusAndDBKeepDB(t, nil,
}.TestOnStatusAndDB(t, nil,
DBTesterWithStatus{
Status: 201,
DBTester: func(app TestApp, assert *assert.A, resp *http.Response) {
Expand All @@ -41,8 +41,7 @@ func CreateSampleCategory(t *testing.T, categoryName string) ExistingAppAssert {
}

func TestCreateCategoryWorks(t *testing.T) {
appAssert := CreateSampleCategory(t, "Science")
appAssert.App.DropDB()
CreateSampleCategory(t, "Science")
}

func TestCreateCategoryIgnoresid(t *testing.T) {
Expand Down Expand Up @@ -73,29 +72,50 @@ func TestCreateCategoryIgnoresid(t *testing.T) {
)
}

func AssertNoCategoryCreation(app TestApp, assert *assert.A, resp *http.Response) {
AssertNumCategoriesRemainsAtN(app, assert, resp, 0)
}

func AssertNumCategoriesRemainsAtN(app TestApp, assert *assert.A, resp *http.Response, n int) {
var categories []models.Category

err := app.Conn.Find(&categories).Error

assert.NilError(err)

assert.Equal(n, len(categories))
}

func TestCreateCategoryFailsIfNameIsNotString(t *testing.T) {
TestRequest{
Method: "POST",
Path: "/api/v1/categories/",
Body: &map[string]interface{}{
"category_name": 1231,
},
}.TestOnStatusAndMessage(t, nil,
MessageWithStatus{
Status: 400,
Message: "failed to process the request",
})
}.TestOnStatusMessageAndDB(t, nil,
StatusMessageDBTester{
MessageWithStatus: MessageWithStatus{
Status: 400,
Message: "failed to process the request",
},
DBTester: AssertNoCategoryCreation,
},
)
}

func TestCreateCategoryFailsIfNameIsMissing(t *testing.T) {
TestRequest{
Method: "POST",
Path: "/api/v1/categories/",
Body: &map[string]interface{}{},
}.TestOnStatusAndMessage(t, nil,
MessageWithStatus{
Status: 400,
Message: "failed to validate the data",
}.TestOnStatusMessageAndDB(t, nil,
StatusMessageDBTester{
MessageWithStatus: MessageWithStatus{
Status: 400,
Message: "failed to validate the data",
},
DBTester: AssertNoCategoryCreation,
},
)
}
Expand All @@ -105,6 +125,10 @@ func TestCreateCategoryFailsIfCategoryWithThatNameAlreadyExists(t *testing.T) {

existingAppAssert := CreateSampleCategory(t, categoryName)

var TestNumCategoriesRemainsAt1 = func(app TestApp, assert *assert.A, resp *http.Response) {
AssertNumCategoriesRemainsAtN(app, assert, resp, 1)
}

for _, permutation := range AllCasingPermutations(categoryName) {
fmt.Println(permutation)
TestRequest{
Expand All @@ -113,13 +137,14 @@ func TestCreateCategoryFailsIfCategoryWithThatNameAlreadyExists(t *testing.T) {
Body: &map[string]interface{}{
"category_name": permutation,
},
}.TestOnStatusAndMessageKeepDB(t, &existingAppAssert,
MessageWithStatus{
Status: 400,
Message: "category with that name already exists",
}.TestOnStatusMessageAndDB(t, &existingAppAssert,
StatusMessageDBTester{
MessageWithStatus: MessageWithStatus{
Status: 400,
Message: "category with that name already exists",
},
DBTester: TestNumCategoriesRemainsAt1,
},
)
}

existingAppAssert.App.DropDB()
}
46 changes: 12 additions & 34 deletions backend/tests/api/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,24 +116,6 @@ func configureDatabase(config config.Settings) (*gorm.DB, error) {
return dbWithDB, nil
}

func (app TestApp) DropDB() {
db, err := app.Conn.DB()

if err != nil {
panic(err)
}

db.Close()

app.Conn, err = gorm.Open(gormPostgres.Open(app.Settings.Database.WithoutDb()), &gorm.Config{SkipDefaultTransaction: true})

if err != nil {
panic(err)
}

app.Conn.Exec(fmt.Sprintf("DROP DATABASE %s;", app.Settings.Database.DatabaseName))
}

type ExistingAppAssert struct {
App TestApp
Assert *assert.A
Expand Down Expand Up @@ -188,10 +170,7 @@ func (request TestRequest) Test(t *testing.T, existingAppAssert *ExistingAppAsse

func (request TestRequest) TestOnStatus(t *testing.T, existingAppAssert *ExistingAppAssert, status int) ExistingAppAssert {
appAssert, resp := request.Test(t, existingAppAssert)
app, assert := appAssert.App, appAssert.Assert
if existingAppAssert != nil {
defer app.DropDB()
}
_, assert := appAssert.App, appAssert.Assert

assert.Equal(status, resp.StatusCode)

Expand All @@ -214,12 +193,6 @@ type MessageWithStatus struct {
}

func (request TestRequest) TestOnStatusAndMessage(t *testing.T, existingAppAssert *ExistingAppAssert, messagedStatus MessageWithStatus) ExistingAppAssert {
appAssert := request.TestOnStatusAndMessageKeepDB(t, existingAppAssert, messagedStatus)
appAssert.App.DropDB()
return appAssert
}

func (request TestRequest) TestOnStatusAndMessageKeepDB(t *testing.T, existingAppAssert *ExistingAppAssert, messagedStatus MessageWithStatus) ExistingAppAssert {
appAssert, resp := request.TestWithJSONBody(t, existingAppAssert)
assert := appAssert.Assert

Expand All @@ -238,6 +211,17 @@ func (request TestRequest) TestOnStatusAndMessageKeepDB(t *testing.T, existingAp
return appAssert
}

type StatusMessageDBTester struct {
MessageWithStatus MessageWithStatus
DBTester DBTester
}

func (request TestRequest) TestOnStatusMessageAndDB(t *testing.T, existingAppAssert *ExistingAppAssert, statusMessageDBTester StatusMessageDBTester) ExistingAppAssert {
appAssert := request.TestOnStatusAndMessage(t, existingAppAssert, statusMessageDBTester.MessageWithStatus)
statusMessageDBTester.DBTester(appAssert.App, appAssert.Assert, nil)
return appAssert
}

type DBTester func(app TestApp, assert *assert.A, resp *http.Response)

type DBTesterWithStatus struct {
Expand All @@ -246,12 +230,6 @@ type DBTesterWithStatus struct {
}

func (request TestRequest) TestOnStatusAndDB(t *testing.T, existingAppAssert *ExistingAppAssert, dbTesterStatus DBTesterWithStatus) ExistingAppAssert {
appAssert := request.TestOnStatusAndDBKeepDB(t, existingAppAssert, dbTesterStatus)
appAssert.App.DropDB()
return appAssert
}

func (request TestRequest) TestOnStatusAndDBKeepDB(t *testing.T, existingAppAssert *ExistingAppAssert, dbTesterStatus DBTesterWithStatus) ExistingAppAssert {
appAssert, resp := request.TestWithJSONBody(t, existingAppAssert)
app, assert := appAssert.App, appAssert.Assert
defer resp.Body.Close()
Expand Down
Loading

0 comments on commit cde14a8

Please sign in to comment.