371 lines
13 KiB
Go
371 lines
13 KiB
Go
package controllers
|
|
|
|
import (
|
|
"GoMembership/internal/config"
|
|
"GoMembership/internal/constants"
|
|
"GoMembership/internal/middlewares"
|
|
"GoMembership/internal/models"
|
|
"GoMembership/internal/services"
|
|
"GoMembership/internal/utils"
|
|
"GoMembership/internal/validation"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/gin-gonic/gin/binding"
|
|
"github.com/go-playground/validator/v10"
|
|
|
|
"GoMembership/pkg/errors"
|
|
"GoMembership/pkg/logger"
|
|
)
|
|
|
|
type UserController struct {
|
|
Service services.UserServiceInterface
|
|
EmailService *services.EmailService
|
|
ConsentService services.ConsentServiceInterface
|
|
BankAccountService services.BankAccountServiceInterface
|
|
MembershipService services.MembershipServiceInterface
|
|
LicenceService services.LicenceServiceInterface
|
|
}
|
|
|
|
type RegistrationData struct {
|
|
User models.User `json:"user"`
|
|
}
|
|
|
|
func (uc *UserController) CurrentUserHandler(c *gin.Context) {
|
|
requestUser, err := uc.Service.FromContext(c)
|
|
if err != nil {
|
|
utils.RespondWithError(c, err, "Error extracting user from context in CurrentUserHandler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"user": requestUser.Safe(),
|
|
})
|
|
}
|
|
|
|
func (uc *UserController) GetAllUsers(c *gin.Context) {
|
|
|
|
requestUser, err := uc.Service.FromContext(c)
|
|
if err != nil {
|
|
utils.RespondWithError(c, err, "Error extracting user from context in UpdateHandler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
|
return
|
|
}
|
|
|
|
if !requestUser.HasPrivilege(constants.Priviliges.View) {
|
|
utils.RespondWithError(c, errors.ErrNotAuthorized, fmt.Sprintf("Not allowed to handle all users. RoleID(%v)<Privilige(%v)", requestUser.RoleID, constants.Priviliges.View), http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
|
return
|
|
}
|
|
|
|
users, err := uc.Service.GetUsers(nil)
|
|
if err != nil {
|
|
utils.RespondWithError(c, err, "Error getting all users", http.StatusInternalServerError, errors.Responses.Fields.User, errors.Responses.Keys.InternalServerError)
|
|
return
|
|
}
|
|
|
|
// Create a slice to hold the safe user representations
|
|
safeUsers := make([]map[string]interface{}, len(*users))
|
|
|
|
// Convert each user to its safe representation
|
|
for i, user := range *users {
|
|
safeUsers[i] = user.Safe()
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"users": users,
|
|
})
|
|
}
|
|
|
|
func (uc *UserController) UpdateHandler(c *gin.Context) {
|
|
// 1. Extract and validate the user ID from the route
|
|
requestUser, err := uc.Service.FromContext(c)
|
|
if err != nil {
|
|
utils.RespondWithError(c, err, "Error extracting user from context in UpdateHandler", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
|
return
|
|
}
|
|
|
|
var updateData RegistrationData
|
|
if err := c.ShouldBindJSON(&updateData); err != nil {
|
|
utils.HandleValidationError(c, err)
|
|
return
|
|
}
|
|
user := updateData.User
|
|
|
|
if !requestUser.HasPrivilege(constants.Priviliges.Update) && user.ID != requestUser.ID {
|
|
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to update user", http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
|
return
|
|
}
|
|
|
|
if requestUser.IsMember() {
|
|
existingUser, err := uc.Service.FromID(&user.ID)
|
|
if err != nil {
|
|
utils.RespondWithError(c, err, "Error finding an existing user", http.StatusNotFound, errors.Responses.Fields.User, errors.Responses.Keys.NotFound)
|
|
return
|
|
}
|
|
// deleting existing Users Password to prevent it from being recognized as changed in any case. (Incoming Password is empty if not changed)
|
|
existingUser.Password = ""
|
|
if err := validation.FilterAllowedStructFields(&user, existingUser, constants.MemberUpdateFields, ""); err != nil {
|
|
if err.Error() == "Not authorized" {
|
|
utils.RespondWithError(c, errors.ErrNotAuthorized, "Trying to update unauthorized fields", http.StatusUnauthorized, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
|
} else {
|
|
utils.RespondWithError(c, err, "Error filtering users input fields", http.StatusInternalServerError, errors.Responses.Fields.User, errors.Responses.Keys.InternalServerError)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
updatedUser, err := uc.Service.Update(&user)
|
|
if err != nil {
|
|
utils.HandleUserUpdateError(c, err)
|
|
return
|
|
}
|
|
|
|
logger.Info.Printf("User %v updated successfully by user %v", updatedUser.Email, requestUser.Email)
|
|
|
|
c.JSON(http.StatusAccepted, gin.H{"message": "User updated successfully", "user": updatedUser.Safe()})
|
|
}
|
|
|
|
func (uc *UserController) DeleteUser(c *gin.Context) {
|
|
|
|
requestUser, err := uc.Service.FromContext(c)
|
|
if err != nil {
|
|
utils.RespondWithError(c, err, "Error extracting user from context in DeleteUser", http.StatusBadRequest, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
|
return
|
|
}
|
|
|
|
type deleteData struct {
|
|
User struct {
|
|
ID uint `json:"id" binding:"required,numeric"`
|
|
LastName string `json:"last_name"`
|
|
} `json:"user"`
|
|
}
|
|
|
|
var data deleteData
|
|
if err := c.ShouldBindJSON(&data); err != nil {
|
|
utils.HandleValidationError(c, err)
|
|
return
|
|
}
|
|
|
|
if !requestUser.HasPrivilege(constants.Priviliges.Delete) && data.User.ID != requestUser.ID {
|
|
utils.RespondWithError(c, errors.ErrNotAuthorized, "Not allowed to delete user", http.StatusForbidden, errors.Responses.Fields.User, errors.Responses.Keys.Unauthorized)
|
|
return
|
|
}
|
|
|
|
logger.Error.Printf("Deleting user: %v", data.User)
|
|
if err := uc.Service.Delete(&data.User.ID); err != nil {
|
|
utils.HandleDeleteUserError(c, err)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "User deleted successfully"})
|
|
}
|
|
|
|
func (uc *UserController) LogoutHandler(c *gin.Context) {
|
|
tokenString, err := c.Cookie("jwt")
|
|
if err != nil {
|
|
logger.Error.Printf("unable to get token from cookie: %#v", err)
|
|
}
|
|
|
|
middlewares.InvalidateSession(tokenString)
|
|
|
|
c.SetCookie("jwt", "", -1, "/", "", true, true)
|
|
c.JSON(http.StatusOK, gin.H{"message": "Logged out successfully"})
|
|
}
|
|
|
|
func (uc *UserController) LoginHandler(c *gin.Context) {
|
|
var input struct {
|
|
Email string `json:"email"`
|
|
Password string `json:"password"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&input); err != nil {
|
|
utils.RespondWithError(c, err, "Invalid JSON or malformed request", http.StatusBadRequest, errors.Responses.Fields.General, errors.Responses.Keys.Invalid)
|
|
return
|
|
}
|
|
|
|
user, err := uc.Service.FromEmail(&input.Email)
|
|
if err != nil {
|
|
utils.RespondWithError(c, err, "Login Error; user not found", http.StatusNotFound,
|
|
errors.Responses.Fields.Login,
|
|
errors.Responses.Keys.UserNotFoundWrongPassword)
|
|
return
|
|
}
|
|
|
|
if !user.IsVerified() {
|
|
utils.RespondWithError(c, fmt.Errorf("User banned from login or not verified %v %v", user.FirstName, user.LastName),
|
|
"Login Error; user is disabled or not verified",
|
|
http.StatusNotAcceptable,
|
|
errors.Responses.Fields.Login,
|
|
errors.Responses.Keys.UserDisabled)
|
|
return
|
|
}
|
|
|
|
ok, err := user.PasswordMatches(input.Password)
|
|
if err != nil {
|
|
utils.RespondWithError(c, err, "Login Error; password incorrect", http.StatusInternalServerError, errors.Responses.Fields.Login, errors.Responses.Keys.InternalServerError)
|
|
return
|
|
}
|
|
if !ok {
|
|
utils.RespondWithError(c, fmt.Errorf("%v %v(%v)", user.FirstName, user.LastName, user.Email),
|
|
"Login Error; wrong password",
|
|
http.StatusNotAcceptable,
|
|
errors.Responses.Fields.Login,
|
|
errors.Responses.Keys.UserNotFoundWrongPassword)
|
|
return
|
|
}
|
|
|
|
// "user_id": user.ID,
|
|
// "role_id": user.RoleID,
|
|
claims := map[string]interface{}{"user_id": user.ID, "role_id": user.RoleID}
|
|
token, err := middlewares.GenerateToken(&config.Auth.JWTSecret, claims, "")
|
|
if err != nil {
|
|
utils.RespondWithError(c, err, "Error generating token in LoginHandler", http.StatusInternalServerError, errors.Responses.Fields.Login, errors.Responses.Keys.JwtGenerationFailed)
|
|
return
|
|
}
|
|
|
|
utils.SetCookie(c, token)
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "Login successful",
|
|
"user_id": user.ID,
|
|
})
|
|
}
|
|
|
|
func (uc *UserController) RegisterUser(c *gin.Context) {
|
|
|
|
var regData RegistrationData
|
|
if err := c.ShouldBindJSON(®Data); err != nil {
|
|
utils.HandleValidationError(c, err)
|
|
return
|
|
}
|
|
|
|
logger.Info.Printf("Registering user %v", regData.User.Email)
|
|
selectedModel, err := uc.MembershipService.GetSubscriptionByName(®Data.User.Membership.SubscriptionModel.Name)
|
|
if err != nil {
|
|
utils.RespondWithError(c, err, "Error in Registeruser, couldn't get selected model", http.StatusNotFound, errors.Responses.Fields.SubscriptionModel, errors.Responses.Keys.InvalidSubscriptionModel)
|
|
return
|
|
}
|
|
regData.User.Membership.SubscriptionModel = *selectedModel
|
|
// Get Gin's binding validator engine with all registered validators
|
|
validate := binding.Validator.Engine().(*validator.Validate)
|
|
|
|
// Validate the populated user struct
|
|
if err := validate.Struct(regData.User); err != nil {
|
|
utils.HandleValidationError(c, err)
|
|
return
|
|
}
|
|
if regData.User.Membership.SubscriptionModel.Name == constants.SupporterSubscriptionModelName {
|
|
regData.User.RoleID = constants.Roles.Supporter
|
|
} else {
|
|
regData.User.RoleID = constants.Roles.Member
|
|
}
|
|
|
|
// Register User
|
|
id, token, err := uc.Service.Register(®Data.User)
|
|
if err != nil {
|
|
if strings.Contains(err.Error(), "UNIQUE constraint failed:") {
|
|
utils.RespondWithError(c, err, "Error in RegisterUser, couldn't register user", http.StatusConflict, errors.Responses.Fields.Email, errors.Responses.Keys.Duplicate)
|
|
} else {
|
|
utils.RespondWithError(c, err, "Error in RegisterUser, couldn't register user", http.StatusConflict, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
|
|
}
|
|
return
|
|
}
|
|
regData.User.ID = id
|
|
|
|
// if this is a supporter don't send mails and he never did give any consent. So stop here
|
|
if regData.User.IsSupporter() {
|
|
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"message": "Supporter Registration successuful",
|
|
"id": regData.User.ID,
|
|
})
|
|
return
|
|
}
|
|
|
|
// Register Consents
|
|
var consents = [2]models.Consent{
|
|
{
|
|
FirstName: regData.User.FirstName,
|
|
LastName: regData.User.LastName,
|
|
Email: regData.User.Email,
|
|
ConsentType: "TermsOfService",
|
|
},
|
|
{
|
|
FirstName: regData.User.FirstName,
|
|
LastName: regData.User.LastName,
|
|
Email: regData.User.Email,
|
|
ConsentType: "Privacy",
|
|
},
|
|
}
|
|
|
|
for _, consent := range consents {
|
|
_, err = uc.ConsentService.RegisterConsent(&consent)
|
|
if err != nil {
|
|
utils.RespondWithError(c, err, "Error in RegisterUser, couldn't register consent", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
logger.Error.Printf("Sending Verification mail to user with id: %#v", id)
|
|
// Send notifications
|
|
if err := uc.EmailService.SendVerificationEmail(®Data.User, &token); err != nil {
|
|
utils.RespondWithError(c, err, "Error in RegisterUser, couldn't send verification email", http.StatusInternalServerError, errors.Responses.Fields.Email, errors.Responses.Keys.UndeliveredVerificationMail)
|
|
// 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",
|
|
"id": regData.User.ID,
|
|
})
|
|
}
|
|
|
|
func (uc *UserController) VerifyMailHandler(c *gin.Context) {
|
|
token := c.Query("token")
|
|
if token == "" {
|
|
logger.Error.Println("Missing token to verify mail")
|
|
c.HTML(http.StatusBadRequest, "verification_error.html", gin.H{"ErrorMessage": "Missing token"})
|
|
return
|
|
}
|
|
userIDint, err := strconv.Atoi(c.Param("id"))
|
|
if err != nil {
|
|
logger.Error.Println("Missing user ID to verify mail")
|
|
c.HTML(http.StatusBadRequest, "verification_error.html", gin.H{"ErrorMessage": "Missing user"})
|
|
return
|
|
}
|
|
userID := uint(userIDint)
|
|
user, err := uc.Service.FromID(&userID)
|
|
if err != nil {
|
|
logger.Error.Printf("Couldn't find user in verifyMailHandler: %#v", err)
|
|
c.HTML(http.StatusBadRequest, "verification_error.html", gin.H{"ErrorMessage": "Couldn't find user"})
|
|
return
|
|
}
|
|
if !user.Verify(token, constants.VerificationTypes.Email) {
|
|
logger.Error.Printf("Couldn't find user verification in verifyMailHandler: %v", err)
|
|
c.HTML(http.StatusBadRequest, "verification_error.html", gin.H{"ErrorMessage": "Couldn't find user verification request"})
|
|
return
|
|
}
|
|
|
|
user.Status = constants.VerifiedStatus
|
|
user.Password = ""
|
|
|
|
updatedUser, err := uc.Service.Update(user)
|
|
if err != nil {
|
|
logger.Error.Printf("Failed to update user(%v) after verification: %v", user.Email, err)
|
|
c.HTML(http.StatusInternalServerError, "verification_error.html", gin.H{"ErrorMessage": "Internal server error, couldn't verify user"})
|
|
return
|
|
}
|
|
logger.Info.Printf("Verified User: %#v", updatedUser.Email)
|
|
|
|
uc.EmailService.SendWelcomeEmail(user)
|
|
c.HTML(http.StatusOK, "verification_success.html", gin.H{"FirstName": user.FirstName})
|
|
}
|