moved to struct validation;

This commit is contained in:
Alex
2024-11-04 17:21:55 +01:00
parent 0fa57bfe75
commit fa10a0a507
15 changed files with 246 additions and 275 deletions

View File

@@ -2,7 +2,7 @@ package controllers
import ( import (
"GoMembership/internal/services" "GoMembership/internal/services"
"GoMembership/pkg/logger" "GoMembership/internal/utils"
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -17,14 +17,9 @@ func (lc *LicenceController) GetAllCategories(c *gin.Context) {
categories, err := lc.Service.GetAllCategories() categories, err := lc.Service.GetAllCategories()
if err != nil { if err != nil {
logger.Error.Printf("Error retrieving licence categories: %v", err) utils.RespondWithError(c, err, "Error retrieving licence categories", http.StatusInternalServerError, "general", "server.error.internal_server_error")
c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{
"field": "general",
"key": "validation.internal_server_error",
}}})
return return
} }
logger.Error.Printf("categories: %v", categories)
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"licence_categories": categories, "licence_categories": categories,
}) })

View File

@@ -7,12 +7,13 @@ import (
"GoMembership/internal/models" "GoMembership/internal/models"
"GoMembership/internal/services" "GoMembership/internal/services"
"GoMembership/internal/utils" "GoMembership/internal/utils"
"GoMembership/internal/validation"
"fmt"
"strings" "strings"
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"GoMembership/pkg/errors" "GoMembership/pkg/errors"
"GoMembership/pkg/logger" "GoMembership/pkg/logger"
@@ -32,49 +33,21 @@ type RegistrationData struct {
} }
func (uc *UserController) CurrentUserHandler(c *gin.Context) { func (uc *UserController) CurrentUserHandler(c *gin.Context) {
userIDInterface, ok := c.Get("user_id") requestUser, err := uc.extractUserFromContext(c)
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))
if err != nil { if err != nil {
logger.Error.Printf("Error retrieving valid user: %v", err) utils.RespondWithError(c, err, "Error extracting user from context in CurrentUserHandler", http.StatusBadRequest, "general", "server.error.internal_server_error")
c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{
"field": "general",
"key": "server.error.internal_server_error",
}}})
return return
} }
c.JSON(http.StatusOK, gin.H{ c.JSON(http.StatusOK, gin.H{
"user": user.Safe(), "user": requestUser.Safe(),
}) })
} }
func (uc *UserController) GetAllUsers(c *gin.Context) { func (uc *UserController) GetAllUsers(c *gin.Context) {
users, err := uc.Service.GetUsers(nil) users, err := uc.Service.GetUsers(nil)
if err != nil { if err != nil {
logger.Error.Printf("Error retrieving users: %v", err) utils.RespondWithError(c, err, "Error getting users in GetAllUsers", http.StatusInternalServerError, "user", "server.error.internal_server_error")
c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{
"field": "general",
"key": "server.error.internal_server_error",
}}})
return return
} }
@@ -84,113 +57,59 @@ func (uc *UserController) GetAllUsers(c *gin.Context) {
} }
func (uc *UserController) UpdateHandler(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 var user models.User
if err := c.ShouldBindJSON(&user); err != nil { if err := c.ShouldBindJSON(&user); err != nil {
logger.Error.Printf("Couldn't decode input: %v", err) utils.HandleValidationError(c, 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",
}}})
return 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) selectedModel, err := uc.MembershipService.GetModelByName(&user.Membership.SubscriptionModel.Name)
if err != nil { if err != nil {
logger.Error.Printf("%v:No subscription model found: %#v", user.Email, err) utils.RespondWithError(c, err, "Error in UpdateHandler", http.StatusNotFound, "subscription_model", "server.validation.subscription_model_not_found")
c.JSON(http.StatusNotFound, gin.H{"errors": []gin.H{{
"field": "subscription_model",
"key": "server.validation.invalid_subscription_model",
}}})
return return
} }
user.Membership.SubscriptionModel = *selectedModel 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 { if err != nil {
switch err { utils.HandleUpdateError(c, err)
case errors.ErrUserNotFound: return
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
}
} }
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) { 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 { if err := c.ShouldBindJSON(&input); err != nil {
logger.Error.Printf("Couldn't decode input: %v", err.Error()) utils.RespondWithError(c, err, "Error in LoginHandler", http.StatusBadRequest, "general", "server.validation.invalid_json")
c.JSON(http.StatusBadRequest, gin.H{"errors": []gin.H{{
"field": "general",
"key": "server.error.invalid_json",
}}})
return return
} }
user, err := uc.Service.GetUserByEmail(input.Email) user, err := uc.Service.GetUserByEmail(input.Email)
if err != nil { if err != nil {
logger.Error.Printf("Error during user(%v) retrieval: %v\n", input.Email, err) utils.RespondWithError(c, err, "Login Error; user not found", http.StatusNotFound,
c.JSON(http.StatusNotFound, gin.H{"errors": []gin.H{{ errors.Responses.Fields.Login,
"field": "login", errors.Responses.Keys.UserNotFoundWrongPassword)
"key": "server.validation.user_not_found_or_wrong_password",
}}})
return return
} }
ok, err := user.PasswordMatches(input.Password) ok, err := user.PasswordMatches(input.Password)
if err != nil { if err != nil {
utils.RespondWithError(c, err, "Login Error; password comparisson failed", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
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",
}}})
return return
} }
if !ok { if !ok {
utils.RespondWithError(c, fmt.Errorf("%v %v", user.FirstName, user.LastName),
logger.Error.Printf("Wrong Password: %v %v", user.FirstName, user.LastName) "Login Error; wrong password",
c.JSON(http.StatusNotAcceptable, gin.H{"errors": []gin.H{{ http.StatusNotAcceptable,
"field": "login", errors.Responses.Fields.Login,
"key": "server.validation.user_not_found_or_wrong_password", errors.Responses.Keys.UserNotFoundWrongPassword)
}}})
return 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, "") token, err := middlewares.GenerateToken(config.Auth.JWTSecret, user, "")
if err != nil { if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{ utils.RespondWithError(c, err, "Error generating token in LoginHandler", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.JwtGenerationFailed)
"field": "general",
"key": "server.error.jwt_generation_failed",
}}})
return return
} }
@@ -272,36 +176,23 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
var regData RegistrationData var regData RegistrationData
if err := c.ShouldBindJSON(&regData); err != nil { if err := c.ShouldBindJSON(&regData); err != nil {
logger.Error.Printf("Couldn't decode Userdata: %v", err) utils.HandleValidationError(c, 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})
return return
} }
logger.Info.Printf("Registering user %v", regData.User.Email)
selectedModel, err := uc.MembershipService.GetModelByName(&regData.User.Membership.SubscriptionModel.Name) selectedModel, err := uc.MembershipService.GetModelByName(&regData.User.Membership.SubscriptionModel.Name)
if err != nil { if err != nil {
logger.Error.Printf("%v:No subscription model found: %#v", regData.User.Email, err) utils.RespondWithError(c, err, "Error in Registeruser, couldn't get selected model", http.StatusNotFound, "subscription_model", "server.validation.subscription_model_not_found")
c.JSON(http.StatusNotFound, gin.H{"errors": []gin.H{{
"field": "subscription_model",
"key": "server.validation.invalid_subscription_model",
}}})
return return
} }
regData.User.Membership.SubscriptionModel = *selectedModel 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 regData.User.RoleID = constants.Roles.Member
// Register User // Register User
@@ -309,16 +200,9 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
if err != nil { if err != nil {
logger.Error.Printf("Couldn't register User(%v): %v", regData.User.Email, err) logger.Error.Printf("Couldn't register User(%v): %v", regData.User.Email, err)
if strings.Contains(err.Error(), "UNIQUE constraint failed: users.email") { if strings.Contains(err.Error(), "UNIQUE constraint failed: users.email") {
c.JSON(http.StatusConflict, gin.H{"errors": []gin.H{{ utils.RespondWithError(c, err, "Error in RegisterUser, couldn't register user", http.StatusConflict, "email", "server.validation.email_already_exists")
"field": "email",
"key": "server.validation.email_already_registered",
}}})
} else { } else {
logger.Error.Printf("Failed to register user: %v", err) utils.RespondWithError(c, err, "Error in RegisterUser, couldn't register user", http.StatusConflict, "general", "server.error.internal_server_error")
c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{
"field": "general",
"key": "server.error.internal_server_error",
}}})
} }
return return
} }
@@ -342,11 +226,7 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
for _, consent := range consents { for _, consent := range consents {
_, err = uc.ConsentService.RegisterConsent(&consent) _, err = uc.ConsentService.RegisterConsent(&consent)
if err != nil { if err != nil {
logger.Error.Printf("%v, Couldn't register consent: %v", regData.User.Email, err) utils.RespondWithError(c, err, "Error in RegisterUser, couldn't register consent", http.StatusInternalServerError, "general", "server.error.internal_server_error")
c.JSON(http.StatusInternalServerError, gin.H{"errors": []gin.H{{
"field": "general",
"key": "server.error.internal_server_error",
}}})
return return
} }
} }
@@ -355,12 +235,14 @@ func (uc *UserController) RegisterUser(c *gin.Context) {
if err := uc.EmailService.SendVerificationEmail(&regData.User, &token); err != nil { if err := uc.EmailService.SendVerificationEmail(&regData.User, &token); err != nil {
logger.Error.Printf("Failed to send email verification email to user(%v): %v", regData.User.Email, err) 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 // Proceed without returning error since user registration is successful
// TODO Notify Admin
} }
// Notify admin of new user registration // Notify admin of new user registration
if err := uc.EmailService.SendRegistrationNotification(&regData.User); err != nil { if err := uc.EmailService.SendRegistrationNotification(&regData.User); err != nil {
logger.Error.Printf("Failed to notify admin of new user(%v) registration: %v", regData.User.Email, err) 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 // Proceed without returning error since user registration is successful
// TODO Notify Admin
} }
c.JSON(http.StatusCreated, gin.H{ c.JSON(http.StatusCreated, gin.H{
"message": "Registration successuful", "message": "Registration successuful",

View File

@@ -221,8 +221,8 @@ func testLoginHandler(t *testing.T) (string, http.Cookie) {
} }
assert.NotEmpty(t, loginCookie) assert.NotEmpty(t, loginCookie)
} else { } else {
assert.Contains(t, response, "error") assert.Contains(t, response, "errors")
assert.NotEmpty(t, response["error"]) assert.NotEmpty(t, response["errors"])
} }
}) })
@@ -585,7 +585,7 @@ func testUpdateUser(t *testing.T, loginCookie http.Cookie) {
}, },
expectedStatus: http.StatusForbidden, expectedStatus: http.StatusForbidden,
expectedErrors: []map[string]string{ 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) { t.Run(tt.name, func(t *testing.T) {
// Create a copy of the user and apply the updates // Create a copy of the user and apply the updates
updatedUser := user updatedUser := user
logger.Error.Printf("user to be updated: %+v", user.Licence)
tt.updateFunc(&updatedUser) tt.updateFunc(&updatedUser)
// Convert user to JSON // Convert user to JSON
jsonData, err := json.Marshal(updatedUser) jsonData, err := json.Marshal(updatedUser)

View File

@@ -51,9 +51,15 @@ func Open(dbPath string, adminMail string) error {
var subscriptionsCount int64 var subscriptionsCount int64
db.Model(&models.SubscriptionModel{}).Count(&subscriptionsCount) db.Model(&models.SubscriptionModel{}).Count(&subscriptionsCount)
if subscriptionsCount == 0 { subscriptionModels := createSubscriptionModels()
subscriptionModels := createSubscriptionModels() for _, model := range subscriptionModels {
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) result := db.Create(&model)
if result.Error != nil { if result.Error != nil {
return result.Error return result.Error

View File

@@ -8,8 +8,8 @@ type BankAccount struct {
MandateDateSigned time.Time `gorm:"not null" json:"mandate_date_signed"` MandateDateSigned time.Time `gorm:"not null" json:"mandate_date_signed"`
Bank string `json:"bank_name" binding:"omitempty,alphanumunicode,safe_content"` Bank string `json:"bank_name" binding:"omitempty,alphanumunicode,safe_content"`
AccountHolderName string `json:"account_holder_name" binding:"omitempty,alphaunicode,safe_content"` AccountHolderName string `json:"account_holder_name" binding:"omitempty,alphaunicode,safe_content"`
IBAN string `gorm:"not null" json:"iban" binding:"iban"` IBAN string `json:"iban"`
BIC string `json:"bic" binding:"omitempty,bic"` BIC string `json:"bic"`
MandateReference string `gorm:"not null" json:"mandate_reference"` MandateReference string `gorm:"not null" json:"mandate_reference"`
ID uint `gorm:"primaryKey"` ID uint `gorm:"primaryKey"`
} }

View File

@@ -18,7 +18,7 @@ func RegisterRoutes(router *gin.Engine, userController *controllers.UserControll
apiRouter := router.Group("/api") apiRouter := router.Group("/api")
apiRouter.Use(middlewares.APIKeyMiddleware()) apiRouter.Use(middlewares.APIKeyMiddleware())
{ {
router.POST("/v1/subscription", membershipcontroller.RegisterSubscription) apiRouter.POST("/v1/subscription", membershipcontroller.RegisterSubscription)
} }
userRouter := router.Group("/backend/users") userRouter := router.Group("/backend/users")

View File

@@ -23,7 +23,7 @@ type UserServiceInterface interface {
GetUserByID(id uint) (*models.User, error) GetUserByID(id uint) (*models.User, error)
GetUsers(where map[string]interface{}) (*[]models.User, error) GetUsers(where map[string]interface{}) (*[]models.User, error)
VerifyUser(token *string) (*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 { type UserService struct {
@@ -31,7 +31,11 @@ type UserService struct {
Licences repositories.LicenceInterface 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 != "" { if user.Password != "" {
setPassword(user.Password, user) setPassword(user.Password, user)

View File

@@ -1,35 +1,48 @@
package utils package utils
import ( import (
"encoding/json" "GoMembership/pkg/errors"
"GoMembership/pkg/logger"
"net/http" "net/http"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
) )
type ResponseHandler struct { func RespondWithError(c *gin.Context, err error, context string, code int, field string, key string) {
Writer http.ResponseWriter 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 { func HandleValidationError(c *gin.Context, err error) {
Status string `json:"status"` var validationErrors []gin.H
Message string `json:"message"` logger.Error.Printf("Sending validation error response Error %v", err.Error())
} if ve, ok := err.(validator.ValidationErrors); ok {
for _, e := range ve {
func NewResponseHandler(w http.ResponseWriter) *ResponseHandler { validationErrors = append(validationErrors, gin.H{
return &ResponseHandler{Writer: w} "field": e.Field(),
} "key": "server.validation." + e.Tag(),
})
func (rh *ResponseHandler) RespondWithError(code int, message string) { }
response := Response{ } else {
Status: "error", validationErrors = append(validationErrors, gin.H{
Message: message, "field": "general",
"key": "server.error.invalid_json",
})
} }
rh.Writer.Header().Set("Content-Type", "application/json") c.JSON(http.StatusBadRequest, gin.H{"errors": validationErrors})
rh.Writer.WriteHeader(code)
json.NewEncoder(rh.Writer).Encode(response)
} }
func (rh *ResponseHandler) RespondWithJSON(code int, payload interface{}) { func HandleUpdateError(c *gin.Context, err error) {
rh.Writer.Header().Set("Content-Type", "application/json") switch err {
rh.Writer.WriteHeader(code) case errors.ErrUserNotFound:
json.NewEncoder(rh.Writer).Encode(payload) 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")
}
} }

View File

@@ -1,13 +1,27 @@
package validation package validation
import ( import (
"GoMembership/internal/models"
"strconv" "strconv"
"time"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
) )
func ValidateLicence(fl validator.FieldLevel) bool { func validateDriverslicence(sl validator.StructLevel) {
fieldValue := fl.Field().String() 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 { if len(fieldValue) != 11 {
return false return false
} }

View File

@@ -1,19 +1,27 @@
package validation package validation
import ( import (
"GoMembership/internal/models"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/jbub/banking/iban" "github.com/jbub/banking/iban"
"github.com/jbub/banking/swift" "github.com/jbub/banking/swift"
) )
func IBANValidator(fl validator.FieldLevel) bool { func validateBankAccount(sl validator.StructLevel) {
fieldValue := fl.Field().String() 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 return iban.Validate(fieldValue) == nil
} }
func BICValidator(fl validator.FieldLevel) bool { func bicValidator(fieldValue string) bool {
fieldValue := fl.Field().String()
return swift.Validate(fieldValue) == nil return swift.Validate(fieldValue) == nil
} }

View File

@@ -3,28 +3,40 @@ package validation
import ( import (
"GoMembership/internal/models" "GoMembership/internal/models"
"GoMembership/internal/repositories" "GoMembership/internal/repositories"
"GoMembership/pkg/errors"
"GoMembership/pkg/logger"
"github.com/go-playground/validator/v10" "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 != "" { if membership.SubscriptionModel.RequiredMembershipField != "" {
switch membership.SubscriptionModel.RequiredMembershipField { switch membership.SubscriptionModel.RequiredMembershipField {
case "ParentMembershipID": case "ParentMembershipID":
if membership.ParentMembershipID == 0 { if err := CheckParentMembershipID(membership); err != nil {
logger.Error.Printf(err.Error())
sl.ReportError(membership.ParentMembershipID, membership.SubscriptionModel.RequiredMembershipField, sl.ReportError(membership.ParentMembershipID, membership.SubscriptionModel.RequiredMembershipField,
"RequiredMembershipField", "required", "") "RequiredMembershipField", "invalid", "")
} else {
_, err := repositories.GetUserByID(&membership.ParentMembershipID)
if err != nil {
sl.ReportError(membership.ParentMembershipID, membership.SubscriptionModel.RequiredMembershipField,
"RequiredMembershipField", "user_id_not_found", "")
}
} }
default: default:
logger.Error.Printf(errors.ErrInvalidValue.Error())
sl.ReportError(membership.ParentMembershipID, membership.SubscriptionModel.RequiredMembershipField, sl.ReportError(membership.ParentMembershipID, membership.SubscriptionModel.RequiredMembershipField,
"RequiredMembershipField", "not_implemented", "") "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
}

View File

@@ -12,9 +12,6 @@ func SetupValidators() {
if v, ok := binding.Validator.Engine().(*validator.Validate); ok { if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
// Register custom validators // Register custom validators
v.RegisterValidation("safe_content", ValidateSafeContent) v.RegisterValidation("safe_content", ValidateSafeContent)
v.RegisterValidation("iban", IBANValidator)
v.RegisterValidation("bic", BICValidator)
v.RegisterValidation("euDriversLicence", ValidateLicence)
// Register struct-level validations // Register struct-level validations
v.RegisterStructValidation(validateUser, models.User{}) v.RegisterStructValidation(validateUser, models.User{})

View File

@@ -16,7 +16,7 @@ func ValidateSubscription(sl validator.StructLevel) {
} }
if sl.Parent().Type().Name() == "MembershipData" { if sl.Parent().Type().Name() == "MembershipData" {
// This is subscription only operation // This is modifying a subscription directly
if subscription.Details == "" { if subscription.Details == "" {
sl.ReportError(subscription.Details, "Details", "details", "required", "") 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 // This is a nested probably user struct. We are only checking if the model exists
existingSubscription, err := repositories.GetModelByName(&subscription.Name) existingSubscription, err := repositories.GetModelByName(&subscription.Name)
if err != nil || existingSubscription == nil { if err != nil || existingSubscription == nil {
sl.ReportError(subscription.Name, "Name", "name", "exists", "") sl.ReportError(subscription.Name, "Subscription_Name", "name", "exists", "")
} }
} }
} }

View File

@@ -1,6 +1,7 @@
package validation package validation
import ( import (
"GoMembership/internal/constants"
"GoMembership/internal/models" "GoMembership/internal/models"
"GoMembership/internal/repositories" "GoMembership/internal/repositories"
"GoMembership/pkg/logger" "GoMembership/pkg/logger"
@@ -12,10 +13,12 @@ import (
func validateUser(sl validator.StructLevel) { func validateUser(sl validator.StructLevel) {
user := sl.Current().Interface().(models.User) 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", "") sl.ReportError(user.DateOfBirth, "DateOfBirth", "date_of_birth", "age", "")
} }
// validate subscriptionModel
if user.Membership.SubscriptionModel.Name == "" { if user.Membership.SubscriptionModel.Name == "" {
sl.ReportError(user.Membership.SubscriptionModel.Name, "SubscriptionModel.Name", "name", "required", "") sl.ReportError(user.Membership.SubscriptionModel.Name, "SubscriptionModel.Name", "name", "required", "")
} else { } else {
@@ -27,25 +30,12 @@ func validateUser(sl validator.StructLevel) {
user.Membership.SubscriptionModel = *selectedModel 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()
// }

View File

@@ -2,6 +2,28 @@ package errors
import "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 ( var (
ErrNotFound = errors.New("not found") ErrNotFound = errors.New("not found")
ErrUserNotFound = errors.New("user not found") ErrUserNotFound = errors.New("user not found")
@@ -18,4 +40,31 @@ var (
ErrValidToken = errors.New("valid token") ErrValidToken = errors.New("valid token")
ErrInvalidUserData = errors.New("invalid user data") ErrInvalidUserData = errors.New("invalid user data")
ErrDuplicateEntry = errors.New("duplicate entry; unique constraint failed") 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",
},
}