package user import ( "errors" "fmt" "image" _ "image/gif" _ "image/jpeg" "image/png" "net/http" "os" "path/filepath" "strconv" "time" "cardverse/internal/pkg/response" "cardverse/internal/pkg/validator" "github.com/gin-gonic/gin" ) type Handler struct { service Service } func NewHandler(service Service) *Handler { return &Handler{service: service} } func (h *Handler) GetProfile(c *gin.Context) { userIDVal, exists := c.Get("user_id") if !exists { response.Error(c, http.StatusUnauthorized, "autentikasi diperlukan", nil) return } userID := userIDVal.(uint) u, err := h.service.GetByID(userID) if err != nil { response.Error(c, http.StatusNotFound, err.Error(), nil) return } response.Success(c, http.StatusOK, "berhasil mengambil profil user", ToUserResponse(*u)) } func (h *Handler) UpdateProfile(c *gin.Context) { userIDVal, exists := c.Get("user_id") if !exists { response.Error(c, http.StatusUnauthorized, "autentikasi diperlukan", nil) return } userID := userIDVal.(uint) var req UpdateProfileRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, http.StatusBadRequest, "input tidak valid", validator.TranslateError(err)) return } updatedUser, err := h.service.UpdateProfile(userID, req) if err != nil { response.Error(c, http.StatusInternalServerError, "gagal mengupdate profil", err.Error()) return } response.Success(c, http.StatusOK, "profil berhasil diupdate", ToUserResponse(*updatedUser)) } func (h *Handler) UploadAvatar(c *gin.Context) { userIDVal, exists := c.Get("user_id") if !exists { response.Error(c, http.StatusUnauthorized, "autentikasi diperlukan", nil) return } userID := userIDVal.(uint) fileHeader, err := c.FormFile("avatar") if err != nil { response.Error(c, http.StatusBadRequest, "file avatar wajib diunggah (form field: avatar)", nil) return } // Batas ukuran file 5 MB if fileHeader.Size > 5*1024*1024 { response.Error(c, http.StatusBadRequest, "ukuran file maksimal 5MB", nil) return } srcFile, err := fileHeader.Open() if err != nil { response.Error(c, http.StatusInternalServerError, "gagal membaca file avatar", err.Error()) return } defer srcFile.Close() // Dekode gambar (support JPG, JPEG, PNG, GIF) img, _, err := image.Decode(srcFile) if err != nil { response.Error(c, http.StatusBadRequest, "file yang diunggah bukan format gambar yang valid", err.Error()) return } // Simpan ke direktori: ./public/images/avatars uploadDir := "./public/images/avatars" if err := os.MkdirAll(uploadDir, os.ModePerm); err != nil { response.Error(c, http.StatusInternalServerError, "gagal membuat direktori penyimpanan avatar", err.Error()) return } filename := fmt.Sprintf("avatar_%d_%d.png", userID, time.Now().UnixNano()) filepathDst := filepath.Join(uploadDir, filename) out, err := os.Create(filepathDst) if err != nil { response.Error(c, http.StatusInternalServerError, "gagal membuat file avatar", err.Error()) return } defer out.Close() // Encode & kompresi ke format PNG encoder := png.Encoder{CompressionLevel: png.BestCompression} if err := encoder.Encode(out, img); err != nil { response.Error(c, http.StatusInternalServerError, "gagal mengompresi gambar ke format PNG", err.Error()) return } avatarURL := fmt.Sprintf("/images/avatars/%s", filename) updatedUser, err := h.service.UpdateAvatar(userID, avatarURL) if err != nil { response.Error(c, http.StatusInternalServerError, "gagal memperbarui avatar user", err.Error()) return } response.Success(c, http.StatusOK, "avatar berhasil diunggah", ToUserResponse(*updatedUser)) } func (h *Handler) Create(c *gin.Context) { creatorID := getContextUserID(c) var req CreateUserRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, http.StatusBadRequest, "input tidak valid", validator.TranslateError(err)) return } newUser, err := h.service.Create(creatorID, req) if err != nil { if errors.Is(err, ErrEmailTaken) { response.Error(c, http.StatusConflict, err.Error(), nil) return } if errors.Is(err, ErrForbiddenRoleCreation) { response.Error(c, http.StatusForbidden, err.Error(), nil) return } response.Error(c, http.StatusInternalServerError, "gagal membuat user", err.Error()) return } response.Success(c, http.StatusCreated, "user berhasil dibuat", ToUserResponse(*newUser)) } func (h *Handler) GetAll(c *gin.Context) { var query ListUserQuery if err := c.ShouldBindQuery(&query); err != nil { response.Error(c, http.StatusBadRequest, "query parameter tidak valid", validator.TranslateError(err)) return } users, total, err := h.service.GetAllPaginated(query) if err != nil { response.Error(c, http.StatusInternalServerError, "gagal mengambil data user", err.Error()) return } pagination := response.NewPagination(query.Page, query.Limit, total) response.SuccessWithPagination(c, http.StatusOK, "berhasil mengambil data user", ToUserResponseList(users), pagination) } func (h *Handler) GetByID(c *gin.Context) { id, err := parseID(c) if err != nil { response.Error(c, http.StatusBadRequest, "id tidak valid", []validator.FieldError{ {Field: "id", Message: "id harus berupa angka"}, }) return } u, err := h.service.GetByID(id) if err != nil { response.Error(c, http.StatusNotFound, err.Error(), nil) return } response.Success(c, http.StatusOK, "berhasil mengambil data user", ToUserResponse(*u)) } func (h *Handler) Update(c *gin.Context) { modifierID := getContextUserIDValue(c) id, err := parseID(c) if err != nil { response.Error(c, http.StatusBadRequest, "id tidak valid", []validator.FieldError{ {Field: "id", Message: "id harus berupa angka"}, }) return } var req UpdateUserRequest if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, http.StatusBadRequest, "input tidak valid", validator.TranslateError(err)) return } updatedUser, err := h.service.Update(modifierID, id, req) if err != nil { if errors.Is(err, ErrEmailTaken) { response.Error(c, http.StatusConflict, err.Error(), nil) return } if errors.Is(err, ErrForbiddenRoleCreation) { response.Error(c, http.StatusForbidden, err.Error(), nil) return } response.Error(c, http.StatusNotFound, err.Error(), nil) return } response.Success(c, http.StatusOK, "user berhasil diupdate", ToUserResponse(*updatedUser)) } func (h *Handler) Suspend(c *gin.Context) { modifierID := getContextUserIDValue(c) id, err := parseID(c) if err != nil { response.Error(c, http.StatusBadRequest, "id tidak valid", []validator.FieldError{ {Field: "id", Message: "id harus berupa angka"}, }) return } updatedUser, err := h.service.SuspendUser(modifierID, id) if err != nil { response.Error(c, http.StatusNotFound, err.Error(), nil) return } response.Success(c, http.StatusOK, "user berhasil disuspensi", ToUserResponse(*updatedUser)) } func (h *Handler) Unsuspend(c *gin.Context) { modifierID := getContextUserIDValue(c) id, err := parseID(c) if err != nil { response.Error(c, http.StatusBadRequest, "id tidak valid", []validator.FieldError{ {Field: "id", Message: "id harus berupa angka"}, }) return } updatedUser, err := h.service.UnsuspendUser(modifierID, id) if err != nil { response.Error(c, http.StatusNotFound, err.Error(), nil) return } response.Success(c, http.StatusOK, "suspensi user berhasil dicabut", ToUserResponse(*updatedUser)) } func (h *Handler) Delete(c *gin.Context) { id, err := parseID(c) if err != nil { response.Error(c, http.StatusBadRequest, "id tidak valid", []validator.FieldError{ {Field: "id", Message: "id harus berupa angka"}, }) return } if err := h.service.Delete(id); err != nil { response.Error(c, http.StatusNotFound, err.Error(), nil) return } response.Success(c, http.StatusOK, "user berhasil dihapus", nil) } func parseID(c *gin.Context) (uint, error) { idParam := c.Param("id") id, err := strconv.ParseUint(idParam, 10, 64) if err != nil { return 0, err } return uint(id), nil } func getContextUserID(c *gin.Context) *uint { if val, exists := c.Get("user_id"); exists { if userID, ok := val.(uint); ok { return &userID } } return nil } func getContextUserIDValue(c *gin.Context) uint { if val, exists := c.Get("user_id"); exists { if userID, ok := val.(uint); ok { return userID } } return 0 }