feat: login register getinfo and update info and update pass
This commit is contained in:
parent
53328a0bde
commit
a31a913658
|
@ -1,5 +1,10 @@
|
||||||
package dto
|
package dto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
type UserResponseDTO struct {
|
type UserResponseDTO struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
|
@ -11,3 +16,79 @@ type UserResponseDTO struct {
|
||||||
CreatedAt string `json:"createdAt"`
|
CreatedAt string `json:"createdAt"`
|
||||||
UpdatedAt string `json:"updatedAt"`
|
UpdatedAt string `json:"updatedAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateUserDTO struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Phone string `json:"phone"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *UpdateUserDTO) Validate() (map[string][]string, bool) {
|
||||||
|
errors := make(map[string][]string)
|
||||||
|
|
||||||
|
if strings.TrimSpace(r.Name) == "" {
|
||||||
|
errors["name"] = append(errors["name"], "Name is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(r.Phone) == "" {
|
||||||
|
errors["phone"] = append(errors["phone"], "Phone number is required")
|
||||||
|
} else if !IsValidPhoneNumber(r.Phone) {
|
||||||
|
errors["phone"] = append(errors["phone"], "Invalid phone number format. Use +62 followed by 9-13 digits")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(r.Email) == "" {
|
||||||
|
errors["email"] = append(errors["email"], "Email is required")
|
||||||
|
} else if !IsValidEmail(r.Email) {
|
||||||
|
errors["email"] = append(errors["email"], "Invalid email format")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
return errors, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsUpdateValidPhoneNumber(phone string) bool {
|
||||||
|
|
||||||
|
re := regexp.MustCompile(`^\+62\d{9,13}$`)
|
||||||
|
return re.MatchString(phone)
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsUPdateValidEmail(email string) bool {
|
||||||
|
|
||||||
|
re := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
|
||||||
|
return re.MatchString(email)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdatePasswordDTO struct {
|
||||||
|
OldPassword string `json:"old_password"`
|
||||||
|
NewPassword string `json:"new_password"`
|
||||||
|
ConfirmNewPassword string `json:"confirm_new_password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *UpdatePasswordDTO) Validate() (map[string][]string, bool) {
|
||||||
|
errors := make(map[string][]string)
|
||||||
|
|
||||||
|
if u.OldPassword == "" {
|
||||||
|
errors["old_password"] = append(errors["old_password"], "Old password is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.NewPassword == "" {
|
||||||
|
errors["new_password"] = append(errors["new_password"], "New password is required")
|
||||||
|
} else if len(u.NewPassword) < 8 {
|
||||||
|
errors["new_password"] = append(errors["new_password"], "Password must be at least 8 characters long")
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.ConfirmNewPassword == "" {
|
||||||
|
errors["confirm_new_password"] = append(errors["confirm_new_password"], "Confirm new password is required")
|
||||||
|
} else if u.NewPassword != u.ConfirmNewPassword {
|
||||||
|
errors["confirm_new_password"] = append(errors["confirm_new_password"], "Passwords do not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
return errors, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, true
|
||||||
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ func (h *UserHandler) Register(c *fiber.Ctx) error {
|
||||||
|
|
||||||
user, err := h.UserService.Register(registerDTO)
|
user, err := h.UserService.Register(registerDTO)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return utils.ErrorResponse(c, err.Error())
|
return utils.GenericErrorResponse(c, fiber.StatusConflict, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
createdAt, _ := utils.FormatDateToIndonesianFormat(user.CreatedAt)
|
createdAt, _ := utils.FormatDateToIndonesianFormat(user.CreatedAt)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/pahmiudahgede/senggoldong/dto"
|
||||||
"github.com/pahmiudahgede/senggoldong/internal/services"
|
"github.com/pahmiudahgede/senggoldong/internal/services"
|
||||||
"github.com/pahmiudahgede/senggoldong/utils"
|
"github.com/pahmiudahgede/senggoldong/utils"
|
||||||
)
|
)
|
||||||
|
@ -28,3 +29,51 @@ func (h *UserProfileHandler) GetUserProfile(c *fiber.Ctx) error {
|
||||||
|
|
||||||
return utils.LogResponse(c, userProfile, "User profile retrieved successfully")
|
return utils.LogResponse(c, userProfile, "User profile retrieved successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *UserProfileHandler) UpdateUserProfile(c *fiber.Ctx) error {
|
||||||
|
var updateData dto.UpdateUserDTO
|
||||||
|
if err := c.BodyParser(&updateData); err != nil {
|
||||||
|
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, ok := c.Locals("userID").(string)
|
||||||
|
if !ok || userID == "" {
|
||||||
|
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
errors, valid := updateData.Validate()
|
||||||
|
if !valid {
|
||||||
|
return utils.ValidationErrorResponse(c, errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
userResponse, err := h.UserProfileService.UpdateUserProfile(userID, updateData)
|
||||||
|
if err != nil {
|
||||||
|
return utils.GenericErrorResponse(c, fiber.StatusConflict, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.LogResponse(c, userResponse, "User profile updated successfully")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *UserProfileHandler) UpdateUserPassword(c *fiber.Ctx) error {
|
||||||
|
var passwordData dto.UpdatePasswordDTO
|
||||||
|
if err := c.BodyParser(&passwordData); err != nil {
|
||||||
|
return utils.ValidationErrorResponse(c, map[string][]string{"body": {"Invalid body"}})
|
||||||
|
}
|
||||||
|
|
||||||
|
userID, ok := c.Locals("userID").(string)
|
||||||
|
if !ok || userID == "" {
|
||||||
|
return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
errors, valid := passwordData.Validate()
|
||||||
|
if !valid {
|
||||||
|
return utils.ValidationErrorResponse(c, errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
userResponse, err := h.UserProfileService.UpdateUserPassword(userID, passwordData)
|
||||||
|
if err != nil {
|
||||||
|
return utils.GenericErrorResponse(c, fiber.StatusBadRequest, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.LogResponse(c, userResponse, "Password updated successfully")
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
type UserProfileRepository interface {
|
type UserProfileRepository interface {
|
||||||
FindByID(userID string) (*model.User, error)
|
FindByID(userID string) (*model.User, error)
|
||||||
|
Update(user *model.User) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type userProfileRepository struct {
|
type userProfileRepository struct {
|
||||||
|
@ -25,3 +26,11 @@ func (r *userProfileRepository) FindByID(userID string) (*model.User, error) {
|
||||||
}
|
}
|
||||||
return &user, nil
|
return &user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *userProfileRepository) Update(user *model.User) error {
|
||||||
|
err := r.DB.Save(user).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -9,13 +9,18 @@ import (
|
||||||
"github.com/pahmiudahgede/senggoldong/dto"
|
"github.com/pahmiudahgede/senggoldong/dto"
|
||||||
"github.com/pahmiudahgede/senggoldong/internal/repositories"
|
"github.com/pahmiudahgede/senggoldong/internal/repositories"
|
||||||
"github.com/pahmiudahgede/senggoldong/utils"
|
"github.com/pahmiudahgede/senggoldong/utils"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserProfileService interface {
|
type UserProfileService interface {
|
||||||
GetUserProfile(userID string) (*dto.UserResponseDTO, error)
|
GetUserProfile(userID string) (*dto.UserResponseDTO, error)
|
||||||
|
UpdateUserProfile(userID string, updateData dto.UpdateUserDTO) (*dto.UserResponseDTO, error)
|
||||||
|
UpdateUserPassword(userID string, passwordData dto.UpdatePasswordDTO) (*dto.UserResponseDTO, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type userProfileService struct {
|
type userProfileService struct {
|
||||||
|
UserRepo repositories.UserRepository
|
||||||
|
RoleRepo repositories.RoleRepository
|
||||||
UserProfileRepo repositories.UserProfileRepository
|
UserProfileRepo repositories.UserProfileRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,3 +76,115 @@ func (s *userProfileService) GetUserProfile(userID string) (*dto.UserResponseDTO
|
||||||
|
|
||||||
return userResponse, nil
|
return userResponse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *userProfileService) UpdateUserProfile(userID string, updateData dto.UpdateUserDTO) (*dto.UserResponseDTO, error) {
|
||||||
|
|
||||||
|
user, err := s.UserProfileRepo.FindByID(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
validationErrors, valid := updateData.Validate()
|
||||||
|
if !valid {
|
||||||
|
return nil, fmt.Errorf("validation failed: %v", validationErrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
if updateData.Name != "" {
|
||||||
|
user.Name = updateData.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if updateData.Phone != "" && updateData.Phone != user.Phone {
|
||||||
|
|
||||||
|
existingPhone, _ := s.UserRepo.FindByPhoneAndRole(updateData.Phone, user.RoleID)
|
||||||
|
if existingPhone != nil {
|
||||||
|
return nil, fmt.Errorf("phone number is already used for this role")
|
||||||
|
}
|
||||||
|
user.Phone = updateData.Phone
|
||||||
|
}
|
||||||
|
|
||||||
|
if updateData.Email != "" && updateData.Email != user.Email {
|
||||||
|
|
||||||
|
existingEmail, _ := s.UserRepo.FindByEmailAndRole(updateData.Email, user.RoleID)
|
||||||
|
if existingEmail != nil {
|
||||||
|
return nil, fmt.Errorf("email is already used for this role")
|
||||||
|
}
|
||||||
|
user.Email = updateData.Email
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.UserProfileRepo.Update(user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update user: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createdAt, _ := utils.FormatDateToIndonesianFormat(user.CreatedAt)
|
||||||
|
updatedAt, _ := utils.FormatDateToIndonesianFormat(user.UpdatedAt)
|
||||||
|
|
||||||
|
userResponse := &dto.UserResponseDTO{
|
||||||
|
ID: user.ID,
|
||||||
|
Username: user.Username,
|
||||||
|
Name: user.Name,
|
||||||
|
Phone: user.Phone,
|
||||||
|
Email: user.Email,
|
||||||
|
EmailVerified: user.EmailVerified,
|
||||||
|
RoleName: user.Role.RoleName,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheKey := fmt.Sprintf("userProfile:%s", userID)
|
||||||
|
cacheData := map[string]interface{}{
|
||||||
|
"data": userResponse,
|
||||||
|
}
|
||||||
|
err = utils.SetJSONData(cacheKey, cacheData, time.Hour*24)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Error updating cached user profile in Redis: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return userResponse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *userProfileService) UpdateUserPassword(userID string, passwordData dto.UpdatePasswordDTO) (*dto.UserResponseDTO, error) {
|
||||||
|
|
||||||
|
validationErrors, valid := passwordData.Validate()
|
||||||
|
if !valid {
|
||||||
|
return nil, fmt.Errorf("validation failed: %v", validationErrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := s.UserProfileRepo.FindByID(userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("user not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !CheckPasswordHash(passwordData.OldPassword, user.Password) {
|
||||||
|
return nil, errors.New("old password is incorrect")
|
||||||
|
}
|
||||||
|
|
||||||
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(passwordData.NewPassword), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to hash new password: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
user.Password = string(hashedPassword)
|
||||||
|
|
||||||
|
err = s.UserProfileRepo.Update(user)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to update password: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createdAt, _ := utils.FormatDateToIndonesianFormat(user.CreatedAt)
|
||||||
|
updatedAt, _ := utils.FormatDateToIndonesianFormat(user.UpdatedAt)
|
||||||
|
|
||||||
|
userResponse := &dto.UserResponseDTO{
|
||||||
|
ID: user.ID,
|
||||||
|
Username: user.Username,
|
||||||
|
Name: user.Name,
|
||||||
|
Phone: user.Phone,
|
||||||
|
Email: user.Email,
|
||||||
|
EmailVerified: user.EmailVerified,
|
||||||
|
RoleName: user.Role.RoleName,
|
||||||
|
CreatedAt: createdAt,
|
||||||
|
UpdatedAt: updatedAt,
|
||||||
|
}
|
||||||
|
|
||||||
|
return userResponse, nil
|
||||||
|
}
|
||||||
|
|
|
@ -15,4 +15,6 @@ func UserProfileRouter(api fiber.Router) {
|
||||||
userProfileHandler := handler.NewUserProfileHandler(userProfileService)
|
userProfileHandler := handler.NewUserProfileHandler(userProfileService)
|
||||||
|
|
||||||
api.Get("/user", middleware.AuthMiddleware, userProfileHandler.GetUserProfile)
|
api.Get("/user", middleware.AuthMiddleware, userProfileHandler.GetUserProfile)
|
||||||
|
api.Put("/user/update-user", middleware.AuthMiddleware, userProfileHandler.UpdateUserProfile)
|
||||||
|
api.Post("/user/update-user-password", middleware.AuthMiddleware, userProfileHandler.UpdateUserPassword)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue