From f534d68f94f95c712fdd79d0b5fc2f3a05939a92 Mon Sep 17 00:00:00 2001 From: Michael Brennan Date: Sun, 21 Jan 2024 16:20:06 -0500 Subject: [PATCH] fix: PATCH UpdateUser, adding requested changes --- backend/src/controllers/user.go | 13 ++++---- backend/src/models/user.go | 10 ++++++ backend/src/services/user.go | 51 +++++++++++++----------------- backend/src/transactions/user.go | 16 +++++++--- backend/src/types/user_params.go | 11 ------- backend/src/utilities/validator.go | 9 +++--- backend/tests/utilities_test.go | 38 ++++++++++++++++++++++ 7 files changed, 93 insertions(+), 55 deletions(-) delete mode 100644 backend/src/types/user_params.go diff --git a/backend/src/controllers/user.go b/backend/src/controllers/user.go index 1b01844da..e6f7b5732 100644 --- a/backend/src/controllers/user.go +++ b/backend/src/controllers/user.go @@ -1,9 +1,8 @@ package controllers import ( + "github.com/GenerateNU/sac/backend/src/models" "github.com/GenerateNU/sac/backend/src/services" - "github.com/GenerateNU/sac/backend/src/types" - "github.com/gofiber/fiber/v2" ) @@ -43,14 +42,16 @@ func (u *UserController) GetAllUsers(c *fiber.Ctx) error { // @Tags user // @Produce json // @Success 200 {object} models.User -// @Failure 404 {string} string "User not found" -// @Failure 400 {string} string "Failed to update user" +// @Failure 404 {string} string "user not found" +// @Failure 400 {string} string "invalid request body" +// @Failure 500 {string} string "database error" +// @Failure 500 {string} string "failed to hash password" // @Router /api/v1/users/:id [patch] func (u *UserController) UpdateUser(c *fiber.Ctx) error { - var user types.UserParams + var user models.UpdateUserRequestBody if err := c.BodyParser(&user); err != nil { - return fiber.NewError(fiber.StatusBadRequest, err.Error()) + return fiber.NewError(fiber.StatusBadRequest, "invalid request body") } userID := c.Params("id") diff --git a/backend/src/models/user.go b/backend/src/models/user.go index 9bf454057..ebd536f01 100644 --- a/backend/src/models/user.go +++ b/backend/src/models/user.go @@ -56,3 +56,13 @@ type User struct { RSVP []Event `gorm:"many2many:user_event_rsvps;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` Waitlist []Event `gorm:"many2many:user_event_waitlists;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;" json:"-" validate:"-"` } + +type UpdateUserRequestBody struct { + NUID string `json:"nuid" validate:"omitempty,number,len=9"` + FirstName string `json:"first_name" validate:"omitempty,max=255"` + LastName string `json:"last_name" validate:"omitempty,max=255"` + Email string `json:"email" validate:"omitempty,email,neu_email"` + Password string `json:"password" validate:"omitempty,password"` + College string `json:"college" validate:"omitempty,oneof=CAMD DMSB KCCS CE BCHS SL CPS CS CSSH"` + Year uint `json:"year" validate:"omitempty,min=1,max=6"` +} diff --git a/backend/src/services/user.go b/backend/src/services/user.go index ef99edec2..d550554a7 100644 --- a/backend/src/services/user.go +++ b/backend/src/services/user.go @@ -1,9 +1,9 @@ package services import ( + "github.com/GenerateNU/sac/backend/src/auth" "github.com/GenerateNU/sac/backend/src/models" "github.com/GenerateNU/sac/backend/src/transactions" - "github.com/GenerateNU/sac/backend/src/types" "github.com/GenerateNU/sac/backend/src/utilities" "github.com/go-playground/validator/v10" "github.com/gofiber/fiber/v2" @@ -11,48 +11,41 @@ import ( ) type UserServiceInterface interface { - CreateUserFromParams(params types.UserParams) (models.User, error) GetAllUsers() ([]models.User, error) - UpdateUser(id string, params types.UserParams) (models.User, error) + UpdateUser(id string, params models.UpdateUserRequestBody) (*models.User, error) } type UserService struct { DB *gorm.DB } -// Creates a models.User from params. This *does not* interact with the database at all; the value will need to be -// passed to gorm.Db.Create(interface{}) for it to be persisted. -func (u *UserService) CreateUserFromParams(params types.UserParams) (models.User, error) { - validate := validator.New() - validate.RegisterValidation("neu_email", utilities.ValidateEmail) - validate.RegisterValidation("password", utilities.ValidatePassword) - if err := validate.Struct(params); err != nil { - return models.User{}, fiber.NewError(fiber.StatusBadRequest, err.Error()) - } - - var user models.User - user.NUID = params.NUID - user.FirstName = params.FirstName - user.LastName = params.LastName - user.Email = params.Email - // TODO: hash - user.PasswordHash = params.Password - user.College = models.College(params.College) - user.Year = models.Year(params.Year) - - return user, nil -} - // Gets all users (including soft deleted users) for testing func (u *UserService) GetAllUsers() ([]models.User, error) { return transactions.GetAllUsers(u.DB) } // Updates a user -func (u *UserService) UpdateUser(id string, params types.UserParams) (models.User, error) { - user, err := u.CreateUserFromParams(params) +func (u *UserService) UpdateUser(id string, params models.UpdateUserRequestBody) (*models.User, error) { + validate := validator.New() + validate.RegisterValidation("neu_email", utilities.ValidateEmail) + validate.RegisterValidation("password", utilities.ValidatePassword) + if err := validate.Struct(params); err != nil { + return nil, fiber.NewError(fiber.StatusBadRequest, "invalid request body") + } + + passwordHash, err := auth.ComputePasswordHash(params.Password) if err != nil { - return models.User{}, err + return nil, fiber.NewError(fiber.StatusInternalServerError, "failed to hash password") + } + + user := models.User{ + NUID: params.NUID, + FirstName: params.FirstName, + LastName: params.LastName, + Email: params.Email, + PasswordHash: *passwordHash, + College: models.College(params.College), + Year: models.Year(params.Year), } return transactions.UpdateUser(u.DB, id, user) diff --git a/backend/src/transactions/user.go b/backend/src/transactions/user.go index 327249fb7..5b2b0bcfb 100644 --- a/backend/src/transactions/user.go +++ b/backend/src/transactions/user.go @@ -1,6 +1,7 @@ package transactions import ( + "errors" "github.com/GenerateNU/sac/backend/src/models" "github.com/gofiber/fiber/v2" "gorm.io/gorm" @@ -16,16 +17,21 @@ func GetAllUsers(db *gorm.DB) ([]models.User, error) { return users, nil } -func UpdateUser(db *gorm.DB, id string, user models.User) (models.User, error) { +func UpdateUser(db *gorm.DB, id string, user models.User) (*models.User, error) { var existingUser models.User - if err := db.First(&existingUser, id).Error; err != nil { - return models.User{}, fiber.NewError(fiber.StatusNotFound, "User not found") + err := db.First(&existingUser, id).Error + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, fiber.NewError(fiber.StatusNotFound, "user not found") + } else { + return nil, fiber.NewError(fiber.StatusInternalServerError, "database error") + } } if err := db.Model(&existingUser).Updates(&user).Error; err != nil { - return models.User{}, fiber.NewError(fiber.StatusBadRequest, "Failed to update user") + return nil, fiber.NewError(fiber.StatusInternalServerError, "database error") } - return existingUser, nil + return &existingUser, nil } diff --git a/backend/src/types/user_params.go b/backend/src/types/user_params.go deleted file mode 100644 index d6376e23b..000000000 --- a/backend/src/types/user_params.go +++ /dev/null @@ -1,11 +0,0 @@ -package types - -type UserParams struct { - NUID string `json:"nuid" validate:"omitempty,number,len=9"` - FirstName string `json:"first_name" validate:"omitempty,max=255"` - LastName string `json:"last_name" validate:"omitempty,max=255"` - Email string `json:"email" validate:"omitempty,email,neu_email"` - Password string `json:"password" validate:"omitempty,password"` - College string `json:"college" validate:"omitempty,oneof=CAMD DMSB KCCS CE BCHS SL CPS CS CSSH"` - Year uint `json:"year" validate:"omitempty,min=1,max=6"` -} diff --git a/backend/src/utilities/validator.go b/backend/src/utilities/validator.go index b5f4f52c9..63fe1cec1 100644 --- a/backend/src/utilities/validator.go +++ b/backend/src/utilities/validator.go @@ -2,6 +2,7 @@ package utilities import ( "github.com/gofiber/fiber/v2" + "regexp" "strconv" "github.com/go-playground/validator/v10" @@ -22,12 +23,12 @@ func ValidateEmail(fl validator.FieldLevel) bool { } func ValidatePassword(fl validator.FieldLevel) bool { - // TODO: we need to think of validation rules - if len(fl.Field().String()) < 6 { + if len(fl.Field().String()) < 8 { return false } - - return true + specialCharactersMatch, _ := regexp.MatchString("[@#%&*+]", fl.Field().String()) + numbersMatch, _ := regexp.MatchString("[0-9]", fl.Field().String()) + return specialCharactersMatch && numbersMatch } // Validate the data sent to the server if the data is invalid, return an error otherwise, return nil diff --git a/backend/tests/utilities_test.go b/backend/tests/utilities_test.go index d7ed0bf33..0c73650b4 100644 --- a/backend/tests/utilities_test.go +++ b/backend/tests/utilities_test.go @@ -1,6 +1,7 @@ package tests import ( + "github.com/go-playground/validator/v10" "testing" "github.com/GenerateNU/sac/backend/src/utilities" @@ -18,3 +19,40 @@ func TestThatContainsWorks(t *testing.T) { assert.Assert(utilities.Contains(slice, "baz")) assert.Assert(!utilities.Contains(slice, "qux")) } + +func TestPasswordValidationWorks(t *testing.T) { + assert := assert.New(t) + + type Thing struct { + password string `validate:"password"` + } + + validate := validator.New() + validate.RegisterValidation("password", utilities.ValidatePassword) + + assert.NilError(validate.Struct(Thing{password: "password!56"})) + assert.NilError(validate.Struct(Thing{password: "cor+ect-h*rse-batte#ry-stap@le-100"})) + assert.NilError(validate.Struct(Thing{password: "1!gooood"})) + assert.Error(validate.Struct(Thing{password: "1!"})) + assert.Error(validate.Struct(Thing{password: "tooshor"})) + assert.Error(validate.Struct(Thing{password: "NoSpecialsOrNumbers"})) +} + +func TestEmailValidationWorks(t *testing.T) { + assert := assert.New(t) + + type Thing struct { + email string `validate:"neu_email"` + } + + validate := validator.New() + validate.RegisterValidation("neu_email", utilities.ValidateEmail) + + assert.NilError(validate.Struct(Thing{email: "brennan.mic@northeastern.edu"})) + assert.NilError(validate.Struct(Thing{email: "blerner@northeastern.edu"})) + assert.NilError(validate.Struct(Thing{email: "validemail@northeastern.edu"})) + assert.Error(validate.Struct(Thing{email: "notanortheasternemail@gmail.com"})) + assert.Error(validate.Struct(Thing{email: "random123@_#!$string"})) + assert.Error(validate.Struct(Thing{email: "local@mail"})) + +}