diff --git a/internal/controllers/licenceController.go b/internal/controllers/licenceController.go index 51c7f3e..a011a85 100644 --- a/internal/controllers/licenceController.go +++ b/internal/controllers/licenceController.go @@ -2,7 +2,7 @@ package controllers import ( "GoMembership/internal/services" - "GoMembership/pkg/logger" + "GoMembership/internal/utils" "net/http" "github.com/gin-gonic/gin" @@ -17,14 +17,9 @@ func (lc *LicenceController) GetAllCategories(c *gin.Context) { categories, err := lc.Service.GetAllCategories() if err != nil { - logger.Error.Printf("Error retrieving licence categories: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{ - "field": "general", - "key": "validation.internal_server_error", - }}}) + utils.RespondWithError(c, err, "Error retrieving licence categories", http.StatusInternalServerError, "general", "server.error.internal_server_error") return } - logger.Error.Printf("categories: %v", categories) c.JSON(http.StatusOK, gin.H{ "licence_categories": categories, }) diff --git a/internal/controllers/user_controller.go b/internal/controllers/user_controller.go index c49b222..1a207e8 100644 --- a/internal/controllers/user_controller.go +++ b/internal/controllers/user_controller.go @@ -7,12 +7,13 @@ import ( "GoMembership/internal/models" "GoMembership/internal/services" "GoMembership/internal/utils" + "GoMembership/internal/validation" + "fmt" "strings" "net/http" "github.com/gin-gonic/gin" - "github.com/go-playground/validator/v10" "GoMembership/pkg/errors" "GoMembership/pkg/logger" @@ -32,49 +33,21 @@ type RegistrationData struct { } func (uc *UserController) CurrentUserHandler(c *gin.Context) { - userIDInterface, ok := c.Get("user_id") - if !ok || userIDInterface == nil { - logger.Error.Printf("Error getting user_id from header") - c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{ - "field": "general", - "key": "server.validation.no_user_id_provided", - }}}) - return - } - userID, ok := userIDInterface.(uint) - - if !ok { - logger.Error.Printf("Error: user_id is not of type uint") - c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{ - "field": "user", - "key": "server.error.internal_server_error", - }}}) - return - } - - user, err := uc.Service.GetUserByID(uint(userID)) + requestUser, err := uc.extractUserFromContext(c) if err != nil { - logger.Error.Printf("Error retrieving valid user: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{ - "field": "general", - "key": "server.error.internal_server_error", - }}}) + utils.RespondWithError(c, err, "Error extracting user from context in CurrentUserHandler", http.StatusBadRequest, "general", "server.error.internal_server_error") return } c.JSON(http.StatusOK, gin.H{ - "user": user.Safe(), + "user": requestUser.Safe(), }) } func (uc *UserController) GetAllUsers(c *gin.Context) { users, err := uc.Service.GetUsers(nil) if err != nil { - logger.Error.Printf("Error retrieving users: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{ - "field": "general", - "key": "server.error.internal_server_error", - }}}) + utils.RespondWithError(c, err, "Error getting users in GetAllUsers", http.StatusInternalServerError, "user", "server.error.internal_server_error") return } @@ -84,113 +57,59 @@ func (uc *UserController) GetAllUsers(c *gin.Context) { } func (uc *UserController) UpdateHandler(c *gin.Context) { + // 1. Extract and validate the user ID from the route + requestUser, err := uc.extractUserFromContext(c) + if err != nil { + utils.RespondWithError(c, err, "Error extracting user from context in UpdateHandler", http.StatusBadRequest, "general", "server.validation.no_auth_tokenw") + return + } + var user models.User if err := c.ShouldBindJSON(&user); err != nil { - logger.Error.Printf("Couldn't decode input: %v", err) - var validationErrors []gin.H - if ve, ok := err.(validator.ValidationErrors); ok { - for _, e := range ve { - - validationErrors = append(validationErrors, gin.H{ - "field": e.Field(), - "key": "server.validation." + e.Tag(), - }) - } - } else { - validationErrors = append(validationErrors, gin.H{ - "field": "general", - "key": "server.error.invalid_json", - }) - } - logger.Error.Printf("ValidationErrors: %#v", validationErrors) - c.JSON(http.StatusBadRequest, gin.H{"errors": validationErrors}) - c.Abort() - return - } - logger.Error.Print("Continuing...") - tokenString, err := c.Cookie("jwt") - if err != nil { - logger.Error.Printf("No Auth token: %v\n", err) - c.JSON(http.StatusUnauthorized, gin.H{"errors": []gin.H{{ - "field": "general", - "key": "server.error.no_auth_token", - }}}) - c.Abort() - return - } - _, claims, err := middlewares.ExtractContentFrom(tokenString) - if err != nil { - - logger.Error.Printf("Error retrieving token and claims from JWT") - c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{ - "field": "general", - "key": "server.error.jwt_parsing_error", - }}}) - return - } - jwtUserID := uint((*claims)["user_id"].(float64)) - userRole := int8((*claims)["role_id"].(float64)) - - if user.ID == 0 { - logger.Error.Printf("No User.ID in request from user with id: %v, aborting", jwtUserID) - c.JSON(http.StatusBadRequest, gin.H{"errors": []gin.H{{ - "field": "id", - "key": "server.validation.no_user_id_provided", - }}}) - return - } - if user.ID != jwtUserID && userRole < constants.Roles.Editor { - c.JSON(http.StatusForbidden, gin.H{"errors": []gin.H{{ - "field": "general", - "key": "server.error.unauthorized_update", - }}}) + utils.HandleValidationError(c, err) return } + if !utils.HasPrivilige(requestUser, constants.Priviliges.Update) && user.ID != requestUser.ID { + utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to update user", http.StatusForbidden, "user", "server.error.unauthorized") + return + } + + // Validate subscription model selectedModel, err := uc.MembershipService.GetModelByName(&user.Membership.SubscriptionModel.Name) if err != nil { - logger.Error.Printf("%v:No subscription model found: %#v", user.Email, err) - c.JSON(http.StatusNotFound, gin.H{"errors": []gin.H{{ - "field": "subscription_model", - "key": "server.validation.invalid_subscription_model", - }}}) + utils.RespondWithError(c, err, "Error in UpdateHandler", http.StatusNotFound, "subscription_model", "server.validation.subscription_model_not_found") return } user.Membership.SubscriptionModel = *selectedModel - // TODO: If it's not an admin, prevent changes to critical fields - // if userRole != constants.Roles.Admin { - // existingUser, err := uc.Service.GetUserByID(jwtUserID) - // if err != nil { - // c.JSON(http.StatusInternalServerError, gin.H{"error": "Error retrieving user data"}) - // return - // } - // user.Email = existingUser.Email - // user.RoleID = existingUser.RoleID - // } - updatedUser, err := uc.Service.UpdateUser(&user, userRole) + updatedUser, err := uc.Service.UpdateUser(&user) if err != nil { - switch err { - case errors.ErrUserNotFound: - c.JSON(http.StatusNotFound, gin.H{"errors": []gin.H{{ - "field": user.FirstName + " " + user.LastName, - "key": "server.validation.user_not_found", - }}}) - case errors.ErrInvalidUserData: - c.JSON(http.StatusBadRequest, gin.H{"errors": []gin.H{{ - "field": "user", - "key": "server.validation.invalid_user_data", - }}}) - default: - logger.Error.Printf("Failed to update user: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{ - "field": "general", - "key": "server.error.internal_server_error", - }}}) - return - } + utils.HandleUpdateError(c, err) + return } - c.JSON(http.StatusAccepted, gin.H{"message": "User updated successfully", "user": updatedUser}) + + logger.Info.Printf("User %d updated successfully by user %d", updatedUser.ID, requestUser.ID) + + c.JSON(http.StatusAccepted, gin.H{"message": "User updated successfully", "user": updatedUser.Safe()}) +} + +func (uc *UserController) extractUserFromContext(c *gin.Context) (*models.User, error) { + + tokenString, err := c.Cookie("jwt") + if err != nil { + return nil, err + } + _, claims, err := middlewares.ExtractContentFrom(tokenString) + if err != nil { + return nil, err + } + jwtUserID := uint((*claims)["user_id"].(float64)) + user, err := uc.Service.GetUserByID(jwtUserID) + if err != nil { + return nil, err + } + return user, nil } func (uc *UserController) LogoutHandler(c *gin.Context) { @@ -212,51 +131,36 @@ func (uc *UserController) LoginHandler(c *gin.Context) { } if err := c.ShouldBindJSON(&input); err != nil { - logger.Error.Printf("Couldn't decode input: %v", err.Error()) - c.JSON(http.StatusBadRequest, gin.H{"errors": []gin.H{{ - "field": "general", - "key": "server.error.invalid_json", - }}}) + utils.RespondWithError(c, err, "Error in LoginHandler", http.StatusBadRequest, "general", "server.validation.invalid_json") return } user, err := uc.Service.GetUserByEmail(input.Email) if err != nil { - logger.Error.Printf("Error during user(%v) retrieval: %v\n", input.Email, err) - c.JSON(http.StatusNotFound, gin.H{"errors": []gin.H{{ - "field": "login", - "key": "server.validation.user_not_found_or_wrong_password", - }}}) + utils.RespondWithError(c, err, "Login Error; user not found", http.StatusNotFound, + errors.Responses.Fields.Login, + errors.Responses.Keys.UserNotFoundWrongPassword) return } ok, err := user.PasswordMatches(input.Password) if err != nil { - - logger.Error.Printf("Error during Password comparison: %v", err.Error()) - c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{ - "field": "general", - "key": "server.error.internal_server_error", - }}}) + utils.RespondWithError(c, err, "Login Error; password comparisson failed", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError) return } if !ok { - - logger.Error.Printf("Wrong Password: %v %v", user.FirstName, user.LastName) - c.JSON(http.StatusNotAcceptable, gin.H{"errors": []gin.H{{ - "field": "login", - "key": "server.validation.user_not_found_or_wrong_password", - }}}) + utils.RespondWithError(c, fmt.Errorf("%v %v", user.FirstName, user.LastName), + "Login Error; wrong password", + http.StatusNotAcceptable, + errors.Responses.Fields.Login, + errors.Responses.Keys.UserNotFoundWrongPassword) return } - logger.Error.Printf("jwtsevret: %v", config.Auth.JWTSecret) + logger.Error.Printf("jwtsecret: %v", config.Auth.JWTSecret) token, err := middlewares.GenerateToken(config.Auth.JWTSecret, user, "") if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{ - "field": "general", - "key": "server.error.jwt_generation_failed", - }}}) + utils.RespondWithError(c, err, "Error generating token in LoginHandler", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.JwtGenerationFailed) return } @@ -272,36 +176,23 @@ func (uc *UserController) RegisterUser(c *gin.Context) { var regData RegistrationData if err := c.ShouldBindJSON(®Data); err != nil { - logger.Error.Printf("Couldn't decode Userdata: %v", err) - var validationErrors []gin.H - if ve, ok := err.(validator.ValidationErrors); ok { - for _, e := range ve { - validationErrors = append(validationErrors, gin.H{ - "field": e.Field(), - "key": "server.validation." + e.Tag(), - }) - } - } else { - validationErrors = append(validationErrors, gin.H{ - "field": "general", - "key": "server.error.invalid_json", - }) - } - c.JSON(http.StatusBadRequest, gin.H{"error": validationErrors}) + utils.HandleValidationError(c, err) return } + logger.Info.Printf("Registering user %v", regData.User.Email) selectedModel, err := uc.MembershipService.GetModelByName(®Data.User.Membership.SubscriptionModel.Name) if err != nil { - logger.Error.Printf("%v:No subscription model found: %#v", regData.User.Email, err) - c.JSON(http.StatusNotFound, gin.H{"errors": []gin.H{{ - "field": "subscription_model", - "key": "server.validation.invalid_subscription_model", - }}}) + utils.RespondWithError(c, err, "Error in Registeruser, couldn't get selected model", http.StatusNotFound, "subscription_model", "server.validation.subscription_model_not_found") return } regData.User.Membership.SubscriptionModel = *selectedModel - + if selectedModel.RequiredMembershipField != "" { + if err := validation.CheckParentMembershipID(regData.User.Membership); err != nil { + utils.RespondWithError(c, err, "Error in RegisterUser, couldn't check parent membership id", http.StatusBadRequest, "parent_membership_id", "server.validation.parent_membership_id_not_found") + return + } + } regData.User.RoleID = constants.Roles.Member // Register User @@ -309,16 +200,9 @@ func (uc *UserController) RegisterUser(c *gin.Context) { if err != nil { logger.Error.Printf("Couldn't register User(%v): %v", regData.User.Email, err) if strings.Contains(err.Error(), "UNIQUE constraint failed: users.email") { - c.JSON(http.StatusConflict, gin.H{"errors": []gin.H{{ - "field": "email", - "key": "server.validation.email_already_registered", - }}}) + utils.RespondWithError(c, err, "Error in RegisterUser, couldn't register user", http.StatusConflict, "email", "server.validation.email_already_exists") } else { - logger.Error.Printf("Failed to register user: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{ - "field": "general", - "key": "server.error.internal_server_error", - }}}) + utils.RespondWithError(c, err, "Error in RegisterUser, couldn't register user", http.StatusConflict, "general", "server.error.internal_server_error") } return } @@ -342,11 +226,7 @@ func (uc *UserController) RegisterUser(c *gin.Context) { for _, consent := range consents { _, err = uc.ConsentService.RegisterConsent(&consent) if err != nil { - logger.Error.Printf("%v, Couldn't register consent: %v", regData.User.Email, err) - c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{ - "field": "general", - "key": "server.error.internal_server_error", - }}}) + utils.RespondWithError(c, err, "Error in RegisterUser, couldn't register consent", http.StatusInternalServerError, "general", "server.error.internal_server_error") return } } @@ -355,12 +235,14 @@ func (uc *UserController) RegisterUser(c *gin.Context) { if err := uc.EmailService.SendVerificationEmail(®Data.User, &token); err != nil { logger.Error.Printf("Failed to send email verification email to user(%v): %v", regData.User.Email, err) // Proceed without returning error since user registration is successful + // TODO Notify Admin } // Notify admin of new user registration if err := uc.EmailService.SendRegistrationNotification(®Data.User); err != nil { logger.Error.Printf("Failed to notify admin of new user(%v) registration: %v", regData.User.Email, err) // Proceed without returning error since user registration is successful + // TODO Notify Admin } c.JSON(http.StatusCreated, gin.H{ "message": "Registration successuful", diff --git a/internal/controllers/user_controller_test.go b/internal/controllers/user_controller_test.go index 369f4a4..1cde957 100644 --- a/internal/controllers/user_controller_test.go +++ b/internal/controllers/user_controller_test.go @@ -221,8 +221,8 @@ func testLoginHandler(t *testing.T) (string, http.Cookie) { } assert.NotEmpty(t, loginCookie) } else { - assert.Contains(t, response, "error") - assert.NotEmpty(t, response["error"]) + assert.Contains(t, response, "errors") + assert.NotEmpty(t, response["errors"]) } }) @@ -585,7 +585,7 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie) { }, expectedStatus: http.StatusForbidden, expectedErrors: []map[string]string{ - {"field": "general", "key": "server.error.unauthorized_update"}, + {"field": "user", "key": "server.error.unauthorized"}, }, }, { @@ -623,6 +623,7 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie) { t.Run(tt.name, func(t *testing.T) { // Create a copy of the user and apply the updates updatedUser := user + logger.Error.Printf("user to be updated: %+v", user.Licence) tt.updateFunc(&updatedUser) // Convert user to JSON jsonData, err := json.Marshal(updatedUser) diff --git a/internal/database/db.go b/internal/database/db.go index 6a47c73..64d408f 100644 --- a/internal/database/db.go +++ b/internal/database/db.go @@ -51,9 +51,15 @@ func Open(dbPath string, adminMail string) error { var subscriptionsCount int64 db.Model(&models.SubscriptionModel{}).Count(&subscriptionsCount) - if subscriptionsCount == 0 { - subscriptionModels := createSubscriptionModels() - for _, model := range subscriptionModels { + subscriptionModels := createSubscriptionModels() + for _, model := range subscriptionModels { + var exists int64 + db. + Model(&models.SubscriptionModel{}). + Where("name = ?", model.Name). + Count(&exists) + logger.Error.Printf("looked for model.name %v and found %v", model.Name, exists) + if exists == 0 { result := db.Create(&model) if result.Error != nil { return result.Error diff --git a/internal/models/bank_account.go b/internal/models/bank_account.go index 7a75517..a9bb8a8 100644 --- a/internal/models/bank_account.go +++ b/internal/models/bank_account.go @@ -8,8 +8,8 @@ type BankAccount struct { MandateDateSigned time.Time `gorm:"not null" json:"mandate_date_signed"` Bank string `json:"bank_name" binding:"omitempty,alphanumunicode,safe_content"` AccountHolderName string `json:"account_holder_name" binding:"omitempty,alphaunicode,safe_content"` - IBAN string `gorm:"not null" json:"iban" binding:"iban"` - BIC string `json:"bic" binding:"omitempty,bic"` + IBAN string `json:"iban"` + BIC string `json:"bic"` MandateReference string `gorm:"not null" json:"mandate_reference"` ID uint `gorm:"primaryKey"` } diff --git a/internal/routes/routes.go b/internal/routes/routes.go index eab221f..95bb2ba 100644 --- a/internal/routes/routes.go +++ b/internal/routes/routes.go @@ -18,7 +18,7 @@ func RegisterRoutes(router *gin.Engine, userController *controllers.UserControll apiRouter := router.Group("/api") apiRouter.Use(middlewares.APIKeyMiddleware()) { - router.POST("/v1/subscription", membershipcontroller.RegisterSubscription) + apiRouter.POST("/v1/subscription", membershipcontroller.RegisterSubscription) } userRouter := router.Group("/backend/users") diff --git a/internal/services/user_service.go b/internal/services/user_service.go index 65b69ad..ada1175 100644 --- a/internal/services/user_service.go +++ b/internal/services/user_service.go @@ -23,7 +23,7 @@ type UserServiceInterface interface { GetUserByID(id uint) (*models.User, error) GetUsers(where map[string]interface{}) (*[]models.User, error) VerifyUser(token *string) (*models.User, error) - UpdateUser(user *models.User, userRole int8) (*models.User, error) + UpdateUser(user *models.User) (*models.User, error) } type UserService struct { @@ -31,7 +31,11 @@ type UserService struct { Licences repositories.LicenceInterface } -func (service *UserService) UpdateUser(user *models.User, userRole int8) (*models.User, error) { +func (service *UserService) UpdateUser(user *models.User) (*models.User, error) { + + if user.ID == 0 { + return nil, errors.ErrUserNotFound + } if user.Password != "" { setPassword(user.Password, user) diff --git a/internal/utils/response_handler.go b/internal/utils/response_handler.go index f271e7e..81655cf 100644 --- a/internal/utils/response_handler.go +++ b/internal/utils/response_handler.go @@ -1,35 +1,48 @@ package utils import ( - "encoding/json" + "GoMembership/pkg/errors" + "GoMembership/pkg/logger" "net/http" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" ) -type ResponseHandler struct { - Writer http.ResponseWriter +func RespondWithError(c *gin.Context, err error, context string, code int, field string, key string) { + logger.Error.Printf("Sending %v Error Response(Field: %v Key: %v) %v: %v", code, field, key, context, err.Error()) + c.JSON(code, gin.H{"errors": []gin.H{{ + "field": field, + "key": key, + }}}) } -type Response struct { - Status string `json:"status"` - Message string `json:"message"` -} - -func NewResponseHandler(w http.ResponseWriter) *ResponseHandler { - return &ResponseHandler{Writer: w} -} - -func (rh *ResponseHandler) RespondWithError(code int, message string) { - response := Response{ - Status: "error", - Message: message, +func HandleValidationError(c *gin.Context, err error) { + var validationErrors []gin.H + logger.Error.Printf("Sending validation error response Error %v", err.Error()) + if ve, ok := err.(validator.ValidationErrors); ok { + for _, e := range ve { + validationErrors = append(validationErrors, gin.H{ + "field": e.Field(), + "key": "server.validation." + e.Tag(), + }) + } + } else { + validationErrors = append(validationErrors, gin.H{ + "field": "general", + "key": "server.error.invalid_json", + }) } - rh.Writer.Header().Set("Content-Type", "application/json") - rh.Writer.WriteHeader(code) - json.NewEncoder(rh.Writer).Encode(response) + c.JSON(http.StatusBadRequest, gin.H{"errors": validationErrors}) } -func (rh *ResponseHandler) RespondWithJSON(code int, payload interface{}) { - rh.Writer.Header().Set("Content-Type", "application/json") - rh.Writer.WriteHeader(code) - json.NewEncoder(rh.Writer).Encode(payload) +func HandleUpdateError(c *gin.Context, err error) { + switch err { + case errors.ErrUserNotFound: + RespondWithError(c, err, "Error while updating user", http.StatusNotFound, "user", "server.validation.user_not_found") + case errors.ErrInvalidUserData: + RespondWithError(c, err, "Error while updating user", http.StatusBadRequest, "user", "server.validation.invalid_user_data") + default: + RespondWithError(c, err, "Error while updating user", http.StatusInternalServerError, "user", "server.error.internal_server_error") + } } diff --git a/internal/validation/DriversLicence_validation.go b/internal/validation/DriversLicence_validation.go index 6277078..f2c564c 100644 --- a/internal/validation/DriversLicence_validation.go +++ b/internal/validation/DriversLicence_validation.go @@ -1,13 +1,27 @@ package validation import ( + "GoMembership/internal/models" "strconv" + "time" "github.com/go-playground/validator/v10" ) -func ValidateLicence(fl validator.FieldLevel) bool { - fieldValue := fl.Field().String() +func validateDriverslicence(sl validator.StructLevel) { + dl := sl.Current().Interface().(models.User).Licence + if !validateLicence(dl.Number) { + sl.ReportError(dl.Number, "licence_number", "", "invalid", "") + } + if dl.IssuedDate.After(time.Now()) { + sl.ReportError(dl.IssuedDate, "issued_date", "", "invalid", "") + } + if dl.ExpirationDate.Before(time.Now().AddDate(0, 0, 3)) { + sl.ReportError(dl.ExpirationDate, "expiration_date", "", "too_soon", "") + } +} + +func validateLicence(fieldValue string) bool { if len(fieldValue) != 11 { return false } diff --git a/internal/validation/bankAccount_validation.go b/internal/validation/bankAccount_validation.go index eb8feb4..a8631ae 100644 --- a/internal/validation/bankAccount_validation.go +++ b/internal/validation/bankAccount_validation.go @@ -1,19 +1,27 @@ package validation import ( + "GoMembership/internal/models" + "github.com/go-playground/validator/v10" "github.com/jbub/banking/iban" "github.com/jbub/banking/swift" ) -func IBANValidator(fl validator.FieldLevel) bool { - fieldValue := fl.Field().String() +func validateBankAccount(sl validator.StructLevel) { + ba := sl.Current().Interface().(models.User).BankAccount + if !ibanValidator(ba.IBAN) { + sl.ReportError(ba.IBAN, "IBAN", "BankAccount.IBAN", "required", "") + } + if ba.BIC != "" && !bicValidator(ba.BIC) { + sl.ReportError(ba.IBAN, "IBAN", "BankAccount.IBAN", "required", "") + } +} +func ibanValidator(fieldValue string) bool { return iban.Validate(fieldValue) == nil } -func BICValidator(fl validator.FieldLevel) bool { - fieldValue := fl.Field().String() - +func bicValidator(fieldValue string) bool { return swift.Validate(fieldValue) == nil } diff --git a/internal/validation/membership_validation.go b/internal/validation/membership_validation.go index 021d39a..08305f5 100644 --- a/internal/validation/membership_validation.go +++ b/internal/validation/membership_validation.go @@ -3,28 +3,40 @@ package validation import ( "GoMembership/internal/models" "GoMembership/internal/repositories" + "GoMembership/pkg/errors" + "GoMembership/pkg/logger" "github.com/go-playground/validator/v10" ) -func validateMembership(sl validator.StructLevel, membership models.Membership) { +func validateMembership(sl validator.StructLevel) { + membership := sl.Current().Interface().(models.User).Membership if membership.SubscriptionModel.RequiredMembershipField != "" { switch membership.SubscriptionModel.RequiredMembershipField { case "ParentMembershipID": - if membership.ParentMembershipID == 0 { + if err := CheckParentMembershipID(membership); err != nil { + logger.Error.Printf(err.Error()) sl.ReportError(membership.ParentMembershipID, membership.SubscriptionModel.RequiredMembershipField, - "RequiredMembershipField", "required", "") - } else { - _, err := repositories.GetUserByID(&membership.ParentMembershipID) - if err != nil { - sl.ReportError(membership.ParentMembershipID, membership.SubscriptionModel.RequiredMembershipField, - "RequiredMembershipField", "user_id_not_found", "") - } + "RequiredMembershipField", "invalid", "") } default: + logger.Error.Printf(errors.ErrInvalidValue.Error()) sl.ReportError(membership.ParentMembershipID, membership.SubscriptionModel.RequiredMembershipField, "RequiredMembershipField", "not_implemented", "") } } } + +func CheckParentMembershipID(membership models.Membership) error { + + if membership.ParentMembershipID == 0 { + return errors.ValErrParentIDNotSet + } else { + _, err := repositories.GetUserByID(&membership.ParentMembershipID) + if err != nil { + return errors.ValErrParentIDNotFound + } + } + return nil +} diff --git a/internal/validation/setup.go b/internal/validation/setup.go index 2668028..5dd5e02 100644 --- a/internal/validation/setup.go +++ b/internal/validation/setup.go @@ -12,9 +12,6 @@ func SetupValidators() { if v, ok := binding.Validator.Engine().(*validator.Validate); ok { // Register custom validators v.RegisterValidation("safe_content", ValidateSafeContent) - v.RegisterValidation("iban", IBANValidator) - v.RegisterValidation("bic", BICValidator) - v.RegisterValidation("euDriversLicence", ValidateLicence) // Register struct-level validations v.RegisterStructValidation(validateUser, models.User{}) diff --git a/internal/validation/subscription_validation.go b/internal/validation/subscription_validation.go index fe154dd..7ba6ed3 100644 --- a/internal/validation/subscription_validation.go +++ b/internal/validation/subscription_validation.go @@ -16,7 +16,7 @@ func ValidateSubscription(sl validator.StructLevel) { } if sl.Parent().Type().Name() == "MembershipData" { - // This is subscription only operation + // This is modifying a subscription directly if subscription.Details == "" { sl.ReportError(subscription.Details, "Details", "details", "required", "") } @@ -40,7 +40,7 @@ func ValidateSubscription(sl validator.StructLevel) { // This is a nested probably user struct. We are only checking if the model exists existingSubscription, err := repositories.GetModelByName(&subscription.Name) if err != nil || existingSubscription == nil { - sl.ReportError(subscription.Name, "Name", "name", "exists", "") + sl.ReportError(subscription.Name, "Subscription_Name", "name", "exists", "") } } } diff --git a/internal/validation/user_validation.go b/internal/validation/user_validation.go index 52644c2..aac625e 100644 --- a/internal/validation/user_validation.go +++ b/internal/validation/user_validation.go @@ -1,6 +1,7 @@ package validation import ( + "GoMembership/internal/constants" "GoMembership/internal/models" "GoMembership/internal/repositories" "GoMembership/pkg/logger" @@ -12,10 +13,12 @@ import ( func validateUser(sl validator.StructLevel) { user := sl.Current().Interface().(models.User) - if user.DateOfBirth.After(time.Now().AddDate(-18, 0, 0)) { + isSuper := user.RoleID >= constants.Roles.Admin + // Validate User > 18 years old + if !isSuper && user.DateOfBirth.After(time.Now().AddDate(-18, 0, 0)) { sl.ReportError(user.DateOfBirth, "DateOfBirth", "date_of_birth", "age", "") } - + // validate subscriptionModel if user.Membership.SubscriptionModel.Name == "" { sl.ReportError(user.Membership.SubscriptionModel.Name, "SubscriptionModel.Name", "name", "required", "") } else { @@ -27,25 +30,12 @@ func validateUser(sl validator.StructLevel) { user.Membership.SubscriptionModel = *selectedModel } } - validateMembership(sl, user.Membership) + + validateMembership(sl) + if !isSuper { + validateBankAccount(sl) + if user.Licence != nil { + validateDriverslicence(sl) + } + } } - -// func RequiredIfNotAdmin(fl validator.FieldLevel) bool { -// // Traverse up the struct hierarchy to find the IsAdmin field -// current := fl.Parent() - -// // Check multiple levels of nesting to find userRole -// for current.IsValid() { -// if isRoleIDField := current.FieldByName("RoleID"); isRoleIDField.IsValid() { -// // If IsAdmin is found and is true, skip validation -// if isRoleIDField.Interface().(int8) == constants.Roles.Admin{ -// return true -// } -// break -// } -// current = current.Parent() // Move to the next parent level -// } - -// If not an admin, enforce that the field must have a non-zero value -// return !fl.Field().IsZero() -// } diff --git a/pkg/errors/errors.go b/pkg/errors/errors.go index 686e53a..325df11 100644 --- a/pkg/errors/errors.go +++ b/pkg/errors/errors.go @@ -2,6 +2,28 @@ package errors import "errors" +type ValidationKeys struct { + Invalid string + InternalServerError string + InvalidJson string + Unauthorized string + InvalidSubscriptionModel string + UserNotFoundWrongPassword string + JwtGenerationFailed string + Duplicate string + InvalidUserID string +} + +type ValidationFields struct { + General string + ParentMemberShipID string + SubscriptionModel string + UserID string + Login string + Email string + User string +} + var ( ErrNotFound = errors.New("not found") ErrUserNotFound = errors.New("user not found") @@ -18,4 +40,31 @@ var ( ErrValidToken = errors.New("valid token") ErrInvalidUserData = errors.New("invalid user data") ErrDuplicateEntry = errors.New("duplicate entry; unique constraint failed") + ErrNotAuthorized = errors.New("not authorized") + ValErrParentIDNotSet = errors.New("Parent Membership ID not provided") + ValErrParentIDNotFound = errors.New("Parent Membership ID not found") ) + +var Responses = struct { + Keys ValidationKeys + Fields ValidationFields +}{ + Keys: ValidationKeys{ + Invalid: "server.validation.invalid", + InternalServerError: "server.error.internal_server_error", + InvalidJson: "server.error.invalid_json", + Unauthorized: "server.error.unauthorized", + UserNotFoundWrongPassword: "server.validation.user_not_found_or_wrong_password", + JwtGenerationFailed: "server.error.jwt_generation_failed", + Duplicate: "server.validation.duplicate", + }, + Fields: ValidationFields{ + General: "general", + ParentMemberShipID: "parent_membership_id", + SubscriptionModel: "subscription_model", + UserID: "user_id", + Login: "login", + Email: "email", + User: "user", + }, +}