package service import ( "errors" "strings" "time" "dating-app/internal/dto" "dating-app/internal/models" "dating-app/internal/repository" ) const MaxPhotosPerUser = 6 var ( ErrTooManyPhotos = errors.New("حداکثر تعداد عکس مجاز رو داری") ErrPhotoNotOwned = errors.New("این عکس متعلق به تو نیست") ErrInvalidImage = errors.New("فرمت فایل باید jpg، png یا webp باشه") ErrImageTooLarge = errors.New("حجم عکس نباید بیشتر از ۵ مگابایت باشه") ) const MaxPhotoSizeBytes = 5 * 1024 * 1024 type ProfileService struct { photoRepo *repository.PhotoRepository userRepo *repository.UserRepository storage PhotoStorage } func NewProfileService(photoRepo *repository.PhotoRepository, userRepo *repository.UserRepository, storage PhotoStorage) *ProfileService { return &ProfileService{photoRepo: photoRepo, userRepo: userRepo, storage: storage} } var allowedImageExts = map[string]bool{ ".jpg": true, ".jpeg": true, ".png": true, ".webp": true, } func (s *ProfileService) UploadPhoto(userID uint, ext string, data []byte) (*models.Photo, error) { if !allowedImageExts[ext] { return nil, ErrInvalidImage } if len(data) > MaxPhotoSizeBytes { return nil, ErrImageTooLarge } count, err := s.photoRepo.CountByUser(userID) if err != nil { return nil, err } if count >= MaxPhotosPerUser { return nil, ErrTooManyPhotos } url, err := s.storage.Save(userID, ext, data) if err != nil { return nil, err } photo := &models.Photo{UserID: userID, URL: url, SortOrder: int(count)} if err := s.photoRepo.Create(photo); err != nil { return nil, err } return photo, nil } func (s *ProfileService) DeletePhoto(userID, photoID uint) error { photo, err := s.photoRepo.FindByID(photoID) if err != nil { return err } if photo.UserID != userID { return ErrPhotoNotOwned } if err := s.photoRepo.Delete(photoID); err != nil { return err } return s.storage.Delete(photo.URL) } // DeletePhotoAdmin مثل DeletePhoto ولی بدون چک مالکیت؛ برای نظارت محتوایی // ادمین که باید بتونه هر عکسی رو صرف‌نظر از صاحبش حذف کنه. func (s *ProfileService) DeletePhotoAdmin(photoID uint) error { photo, err := s.photoRepo.FindByID(photoID) if err != nil { return err } if err := s.photoRepo.Delete(photoID); err != nil { return err } return s.storage.Delete(photo.URL) } func (s *ProfileService) ListPhotos(userID uint) ([]models.Photo, error) { return s.photoRepo.ListByUser(userID) } func (s *ProfileService) UpdateLocation(user *models.User, lat, lng float64) error { return s.userRepo.UpdateFields(user, map[string]interface{}{ "latitude": lat, "longitude": lng, }) } // UpdateBio بیوگرافی کوتاه پروفایل رو ذخیره می‌کنه (طول توسط binding در DTO چک شده) func (s *ProfileService) UpdateBio(user *models.User, bio string) error { bio = strings.TrimSpace(bio) if err := s.userRepo.UpdateFields(user, map[string]interface{}{"bio": bio}); err != nil { return err } user.Bio = &bio return nil } // CheckIn ورود روزانه‌ی کاربر رو ثبت می‌کنه و استریک (چند روز پشت‌سرهم) رو // به‌روز می‌کنه: اگه دیروز هم اومده بوده، استریک +۱ می‌شه؛ اگه امروز قبلاً // چک‌این کرده، چیزی تغییر نمی‌کنه؛ وگرنه (وقفه افتاده) استریک از ۱ شروع می‌شه. func (s *ProfileService) CheckIn(user *models.User) (*dto.CheckInResult, error) { now := time.Now() today := now.Format("2006-01-02") if user.LastActiveDate != nil && user.LastActiveDate.Format("2006-01-02") == today { return &dto.CheckInResult{StreakCount: user.StreakCount, IsNewToday: false}, nil } newStreak := 1 if user.LastActiveDate != nil { yesterday := now.AddDate(0, 0, -1).Format("2006-01-02") if user.LastActiveDate.Format("2006-01-02") == yesterday { newStreak = user.StreakCount + 1 } } if err := s.userRepo.UpdateFields(user, map[string]interface{}{ "streak_count": newStreak, "last_active_date": now, }); err != nil { return nil, err } user.StreakCount = newStreak user.LastActiveDate = &now return &dto.CheckInResult{StreakCount: newStreak, IsNewToday: true}, nil }