diff --git a/dto/user_dto.go b/dto/user_dto.go index 784a232..4c442b5 100644 --- a/dto/user_dto.go +++ b/dto/user_dto.go @@ -8,6 +8,7 @@ import ( type UserResponseDTO struct { ID string `json:"id"` Username string `json:"username"` + Avatar *string `json:"photoprofile,omitempty"` Name string `json:"name"` Phone string `json:"phone"` Email string `json:"email"` diff --git a/internal/handler/user_handler.go b/internal/handler/user_handler.go index 7b54f2b..51f1d83 100644 --- a/internal/handler/user_handler.go +++ b/internal/handler/user_handler.go @@ -77,3 +77,22 @@ func (h *UserProfileHandler) UpdateUserPassword(c *fiber.Ctx) error { return utils.LogResponse(c, userResponse, "Password updated successfully") } + +func (h *UserProfileHandler) UpdateUserAvatar(c *fiber.Ctx) error { + userID, ok := c.Locals("userID").(string) + if !ok || userID == "" { + return utils.GenericErrorResponse(c, fiber.StatusUnauthorized, "Unauthorized: User session not found") + } + + file, err := c.FormFile("avatar") + if err != nil { + return utils.GenericErrorResponse(c, fiber.StatusBadRequest, "No avatar file uploaded") + } + + userResponse, err := h.UserProfileService.UpdateUserAvatar(userID, file) + if err != nil { + return utils.GenericErrorResponse(c, fiber.StatusInternalServerError, err.Error()) + } + + return utils.LogResponse(c, userResponse, "Avatar updated successfully") +} diff --git a/internal/repositories/user_repo.go b/internal/repositories/user_repo.go index 4f80f4d..57c2485 100644 --- a/internal/repositories/user_repo.go +++ b/internal/repositories/user_repo.go @@ -8,6 +8,7 @@ import ( type UserProfileRepository interface { FindByID(userID string) (*model.User, error) Update(user *model.User) error + UpdateAvatar(userID, avatarURL string) error } type userProfileRepository struct { @@ -34,3 +35,12 @@ func (r *userProfileRepository) Update(user *model.User) error { } return nil } + +func (r *userProfileRepository) UpdateAvatar(userID, avatarURL string) error { + var user model.User + err := r.DB.Model(&user).Where("id = ?", userID).Update("avatar", avatarURL).Error + if err != nil { + return err + } + return nil +} \ No newline at end of file diff --git a/internal/services/user_service.go b/internal/services/user_service.go index 5ed3ff4..0509447 100644 --- a/internal/services/user_service.go +++ b/internal/services/user_service.go @@ -4,6 +4,9 @@ import ( "encoding/json" "errors" "fmt" + "mime/multipart" + "os" + "path/filepath" "time" "github.com/pahmiudahgede/senggoldong/dto" @@ -16,6 +19,7 @@ type UserProfileService interface { GetUserProfile(userID string) (*dto.UserResponseDTO, error) UpdateUserProfile(userID string, updateData dto.UpdateUserDTO) (*dto.UserResponseDTO, error) UpdateUserPassword(userID string, passwordData dto.UpdatePasswordDTO) (*dto.UserResponseDTO, error) + UpdateUserAvatar(userID string, file *multipart.FileHeader) (*dto.UserResponseDTO, error) } type userProfileService struct { @@ -188,3 +192,69 @@ func (s *userProfileService) UpdateUserPassword(userID string, passwordData dto. return userResponse, nil } + +func (s *userProfileService) UpdateUserAvatar(userID string, file *multipart.FileHeader) (*dto.UserResponseDTO, error) { + + avatarDir := "./public/uploads/avatars" + if _, err := os.Stat(avatarDir); os.IsNotExist(err) { + err := os.MkdirAll(avatarDir, os.ModePerm) + if err != nil { + return nil, fmt.Errorf("failed to create avatar directory: %v", err) + } + } + + extension := filepath.Ext(file.Filename) + if extension != ".jpg" && extension != ".jpeg" && extension != ".png" { + return nil, fmt.Errorf("invalid file type, only .jpg, .jpeg, and .png are allowed") + } + + avatarFileName := fmt.Sprintf("%s_avatar%s", userID, extension) + avatarPath := filepath.Join(avatarDir, avatarFileName) + + src, err := file.Open() + if err != nil { + return nil, fmt.Errorf("failed to open uploaded file: %v", err) + } + defer src.Close() + + dst, err := os.Create(avatarPath) + if err != nil { + return nil, fmt.Errorf("failed to create file: %v", err) + } + defer dst.Close() + + _, err = dst.ReadFrom(src) + if err != nil { + return nil, fmt.Errorf("failed to save avatar file: %v", err) + } + + avatarURL := fmt.Sprintf("/uploads/avatars/%s", avatarFileName) + + err = s.UserProfileRepo.UpdateAvatar(userID, avatarURL) + if err != nil { + return nil, fmt.Errorf("failed to update avatar in the database: %v", err) + } + + updatedUser, err := s.UserProfileRepo.FindByID(userID) + if err != nil { + return nil, fmt.Errorf("failed to retrieve updated user data: %v", err) + } + + createdAt, _ := utils.FormatDateToIndonesianFormat(updatedUser.CreatedAt) + updatedAt, _ := utils.FormatDateToIndonesianFormat(updatedUser.UpdatedAt) + + userResponse := &dto.UserResponseDTO{ + ID: updatedUser.ID, + Username: updatedUser.Username, + Avatar: updatedUser.Avatar, + Name: updatedUser.Name, + Phone: updatedUser.Phone, + Email: updatedUser.Email, + EmailVerified: updatedUser.EmailVerified, + RoleName: updatedUser.Role.RoleName, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + } + + return userResponse, nil +} diff --git a/presentation/user_route.go b/presentation/user_route.go index c4fc7ac..d9befc6 100644 --- a/presentation/user_route.go +++ b/presentation/user_route.go @@ -17,4 +17,5 @@ func UserProfileRouter(api fiber.Router) { 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) + api.Put("/user/upload-photoprofile", middleware.AuthMiddleware, userProfileHandler.UpdateUserAvatar) } diff --git a/public/uploads/avatars/90bae8f6-1e5d-48ea-b653-6e7e04bd4142_avatar.png b/public/uploads/avatars/90bae8f6-1e5d-48ea-b653-6e7e04bd4142_avatar.png new file mode 100644 index 0000000..67cd16e Binary files /dev/null and b/public/uploads/avatars/90bae8f6-1e5d-48ea-b653-6e7e04bd4142_avatar.png differ