package handler import ( "errors" "io" "net/http" "path/filepath" "strconv" "strings" "github.com/gin-gonic/gin" "gorm.io/gorm" "dating-app/internal/dto" "dating-app/internal/repository" "dating-app/internal/service" "dating-app/internal/utils" ) type ProfileHandler struct { optionRepo *repository.OptionRepository questionRepo *repository.QuestionRepository photoRepo *repository.PhotoRepository onboardingService *service.OnboardingService profileService *service.ProfileService } func NewProfileHandler( optionRepo *repository.OptionRepository, questionRepo *repository.QuestionRepository, photoRepo *repository.PhotoRepository, onboardingService *service.OnboardingService, profileService *service.ProfileService, ) *ProfileHandler { return &ProfileHandler{ optionRepo: optionRepo, questionRepo: questionRepo, photoRepo: photoRepo, onboardingService: onboardingService, profileService: profileService, } } // Me GET /profile/me — پروفایل کامل کاربر شامل تمام انتخاب‌ها و پاسخ‌ها func (h *ProfileHandler) Me(c *gin.Context) { user, ok := currentUser(c) if !ok { return } payload := fullProfilePayload(user, h.optionRepo, h.questionRepo, h.onboardingService, h.photoRepo) utils.Success(c, http.StatusOK, "اطلاعات پروفایل با موفقیت دریافت شد", payload) } // UploadPhoto POST /profile/photos (multipart/form-data, فیلد "photo") func (h *ProfileHandler) UploadPhoto(c *gin.Context) { user, ok := currentUser(c) if !ok { return } fileHeader, err := c.FormFile("photo") if err != nil { utils.Fail(c, http.StatusBadRequest, "فایل عکس ارسال نشده", nil) return } ext := strings.ToLower(filepath.Ext(fileHeader.Filename)) file, err := fileHeader.Open() if err != nil { utils.Fail(c, http.StatusBadRequest, "خطا در خواندن فایل", nil) return } defer file.Close() data, err := io.ReadAll(file) if err != nil { utils.Fail(c, http.StatusBadRequest, "خطا در خواندن فایل", nil) return } photo, err := h.profileService.UploadPhoto(user.ID, ext, data) if err != nil { h.handlePhotoError(c, err) return } utils.Success(c, http.StatusCreated, "عکس اضافه شد", photo) } // DeletePhoto DELETE /profile/photos/:id func (h *ProfileHandler) DeletePhoto(c *gin.Context) { user, ok := currentUser(c) if !ok { return } id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { utils.Fail(c, http.StatusBadRequest, "شناسه نامعتبر است", nil) return } if err := h.profileService.DeletePhoto(user.ID, uint(id)); err != nil { h.handlePhotoError(c, err) return } utils.Success(c, http.StatusOK, "عکس حذف شد", nil) } func (h *ProfileHandler) handlePhotoError(c *gin.Context, err error) { switch { case errors.Is(err, gorm.ErrRecordNotFound): utils.Fail(c, http.StatusNotFound, "عکس یافت نشد", nil) case errors.Is(err, service.ErrPhotoNotOwned): utils.Fail(c, http.StatusForbidden, err.Error(), nil) case errors.Is(err, service.ErrTooManyPhotos), errors.Is(err, service.ErrInvalidImage), errors.Is(err, service.ErrImageTooLarge): utils.Fail(c, http.StatusBadRequest, err.Error(), nil) default: utils.Fail(c, http.StatusInternalServerError, "خطا در ذخیره‌ی عکس", nil) } } // UpdateLocation PATCH /profile/location func (h *ProfileHandler) UpdateLocation(c *gin.Context) { user, ok := currentUser(c) if !ok { return } var req dto.UpdateLocationRequest if err := c.ShouldBindJSON(&req); err != nil { utils.Fail(c, http.StatusBadRequest, "ورودی نامعتبر است", err.Error()) return } if err := h.profileService.UpdateLocation(user, req.Latitude, req.Longitude); err != nil { utils.Fail(c, http.StatusInternalServerError, "خطا در ذخیره‌ی موقعیت مکانی", nil) return } utils.Success(c, http.StatusOK, "موقعیت مکانی ثبت شد", nil) } // UpdateBio PATCH /profile/bio body: {bio} func (h *ProfileHandler) UpdateBio(c *gin.Context) { user, ok := currentUser(c) if !ok { return } var req dto.UpdateBioRequest if err := c.ShouldBindJSON(&req); err != nil { utils.Fail(c, http.StatusBadRequest, "بیوگرافی نباید بیشتر از ۳۰۰ کاراکتر باشه", err.Error()) return } if err := h.profileService.UpdateBio(user, req.Bio); err != nil { utils.Fail(c, http.StatusInternalServerError, "خطا در ذخیره‌ی بیوگرافی", nil) return } utils.Success(c, http.StatusOK, "بیوگرافی ذخیره شد", nil) } // CheckIn POST /profile/checkin — ثبت ورود روزانه برای استریک گیمیفیکیشن func (h *ProfileHandler) CheckIn(c *gin.Context) { user, ok := currentUser(c) if !ok { return } result, err := h.profileService.CheckIn(user) if err != nil { utils.Fail(c, http.StatusInternalServerError, "خطا در ثبت ورود روزانه", nil) return } utils.Success(c, http.StatusOK, "", result) } // UpdateNotificationSettings PATCH /profile/notifications func (h *ProfileHandler) UpdateNotificationSettings(c *gin.Context) { user, ok := currentUser(c) if !ok { return } var req dto.UpdateNotificationSettingsRequest if err := c.ShouldBindJSON(&req); err != nil { utils.Fail(c, http.StatusBadRequest, "ورودی نامعتبر است", err.Error()) return } if err := h.profileService.UpdateNotificationSettings(user, req); err != nil { utils.Fail(c, http.StatusInternalServerError, "خطا در ذخیره‌ی تنظیمات اعلان", nil) return } utils.Success(c, http.StatusOK, "تنظیمات اعلان ذخیره شد", nil) } // DeleteAccount DELETE /profile/me — غیرفعال کردن (soft delete) حساب کاربر func (h *ProfileHandler) DeleteAccount(c *gin.Context) { user, ok := currentUser(c) if !ok { return } if err := h.profileService.DeleteAccount(user); err != nil { utils.Fail(c, http.StatusInternalServerError, "خطا در حذف حساب", nil) return } utils.Success(c, http.StatusOK, "حساب کاربری غیرفعال شد", nil) } // UpdatePublicKey PUT /profile/public-key — ثبت/جایگزینی کلید عمومی X25519 // برای رمزنگاری سرتاسری پیام‌ها. کلاینت این کلید رو خودش تولید می‌کنه؛ کلید // خصوصی متناظرش هیچ‌وقت به این endpoint (یا هیچ endpoint دیگه‌ای) فرستاده نمی‌شه. func (h *ProfileHandler) UpdatePublicKey(c *gin.Context) { user, ok := currentUser(c) if !ok { return } var req dto.UpdatePublicKeyRequest if err := c.ShouldBindJSON(&req); err != nil { utils.Fail(c, http.StatusBadRequest, "کلید عمومی نامعتبر است", err.Error()) return } if err := h.profileService.UpdatePublicKey(user, req.PublicKey); err != nil { if errors.Is(err, service.ErrInvalidPublicKey) { utils.Fail(c, http.StatusBadRequest, err.Error(), nil) return } utils.Fail(c, http.StatusInternalServerError, "خطا در ذخیره‌ی کلید عمومی", nil) return } utils.Success(c, http.StatusOK, "کلید عمومی ذخیره شد", nil) }