package repository import ( "errors" "time" "gorm.io/gorm" "dating-app/internal/cache" "dating-app/internal/models" ) var ErrCategoryNotFound = errors.New("دسته‌ی موردنظر یافت نشد") // activeOptionsCacheTTL: گزینه‌های فعال هر دسته و خودِ رکورد دسته فقط از پنل // ادمین تغییر می‌کنن (که خیلی به‌ندرت اتفاق می‌افته) ولی توی هر مرحله‌ی // آنبوردینگ/دیسکاوری هر کاربر خونده می‌شن؛ کش کردنشون فشار روی دیتابیس رو // زیاد کم می‌کنه. const activeOptionsCacheTTL = 5 * time.Minute type OptionRepository struct { db *gorm.DB activeOptionsByKey *cache.TTLCache[models.CategoryKey, []models.Option] categoryByKey *cache.TTLCache[models.CategoryKey, models.Category] } func NewOptionRepository(db *gorm.DB) *OptionRepository { return &OptionRepository{ db: db, activeOptionsByKey: cache.New[models.CategoryKey, []models.Option](activeOptionsCacheTTL), categoryByKey: cache.New[models.CategoryKey, models.Category](activeOptionsCacheTTL), } } func (r *OptionRepository) GetCategoryByKey(key models.CategoryKey) (*models.Category, error) { if cached, ok := r.categoryByKey.Get(key); ok { return &cached, nil } var cat models.Category err := r.db.Where("key = ?", key).First(&cat).Error if err == gorm.ErrRecordNotFound { return nil, ErrCategoryNotFound } if err != nil { return nil, err } r.categoryByKey.Set(key, cat) return &cat, nil } func (r *OptionRepository) UpdateCategory(cat *models.Category) error { if err := r.db.Save(cat).Error; err != nil { return err } r.activeOptionsByKey.Delete(cat.Key) r.categoryByKey.Delete(cat.Key) return nil } func (r *OptionRepository) ListActiveOptionsByCategoryKey(key models.CategoryKey) ([]models.Option, error) { if cached, ok := r.activeOptionsByKey.Get(key); ok { return cached, nil } cat, err := r.GetCategoryByKey(key) if err != nil { return nil, err } var options []models.Option err = r.db.Where("category_id = ? AND is_active = ?", cat.ID, true). Order("sort_order asc, id asc"). Find(&options).Error if err != nil { return nil, err } r.activeOptionsByKey.Set(key, options) return options, nil } // ListOptionsByCategoryKeyAdmin تمام گزینه‌ها (فعال و غیرفعال) رو برمی‌گردونه - برای پنل ادمین func (r *OptionRepository) ListOptionsByCategoryKeyAdmin(key models.CategoryKey) ([]models.Option, error) { cat, err := r.GetCategoryByKey(key) if err != nil { return nil, err } var options []models.Option err = r.db.Where("category_id = ?", cat.ID). Order("sort_order asc, id asc"). Find(&options).Error return options, err } func (r *OptionRepository) CreateOption(opt *models.Option) error { if err := r.db.Create(opt).Error; err != nil { return err } r.activeOptionsByKey.Clear() return nil } func (r *OptionRepository) FindOptionByID(id uint) (*models.Option, error) { var opt models.Option err := r.db.First(&opt, id).Error if err != nil { return nil, err } return &opt, nil } func (r *OptionRepository) UpdateOption(opt *models.Option) error { if err := r.db.Save(opt).Error; err != nil { return err } r.activeOptionsByKey.Clear() return nil } func (r *OptionRepository) DeleteOption(id uint) error { if err := r.db.Delete(&models.Option{}, id).Error; err != nil { return err } r.activeOptionsByKey.Clear() return nil } // FindActiveOptionsByIDsInCategory فقط گزینه‌های فعالی که در این دسته هستند و id شون // داخل لیست داده‌شده است رو برمی‌گردونه (برای اعتبارسنجی انتخاب کاربر) func (r *OptionRepository) FindActiveOptionsByIDsInCategory(categoryID uint, ids []uint) ([]models.Option, error) { var options []models.Option err := r.db.Where("category_id = ? AND is_active = ? AND id IN ?", categoryID, true, ids). Find(&options).Error return options, err } // ReplaceUserSelections تمام انتخاب‌های قبلی کاربر در یک دسته را با لیست جدید جایگزین می‌کند (تراکنشی) func (r *OptionRepository) ReplaceUserSelections(userID, categoryID uint, optionIDs []uint) error { return r.db.Transaction(func(tx *gorm.DB) error { if err := tx.Where("user_id = ? AND category_id = ?", userID, categoryID). Delete(&models.UserOptionSelection{}).Error; err != nil { return err } for _, optID := range optionIDs { sel := models.UserOptionSelection{ UserID: userID, CategoryID: categoryID, OptionID: optID, } if err := tx.Create(&sel).Error; err != nil { return err } } return nil }) } // GetUserSelections انتخاب‌های کاربر در یک دسته را همراه با اطلاعات گزینه برمی‌گرداند func (r *OptionRepository) GetUserSelections(userID, categoryID uint) ([]models.UserOptionSelection, error) { var selections []models.UserOptionSelection err := r.db.Preload("Option"). Where("user_id = ? AND category_id = ?", userID, categoryID). Find(&selections).Error return selections, err } // CountUserSelections تعداد انتخاب‌های ثبت‌شده‌ی کاربر در یک دسته را برمی‌گرداند // (برای بررسی این‌که مرحله تکمیل شده یا نه) func (r *OptionRepository) CountUserSelections(userID, categoryID uint) (int64, error) { var count int64 err := r.db.Model(&models.UserOptionSelection{}). Where("user_id = ? AND category_id = ?", userID, categoryID). Count(&count).Error return count, err } // GetSelectionsForUsersInCategory انتخاب‌های چند کاربر رو در یک دسته‌ی مشخص، // همه با هم در یک کوئری برمی‌گردونه (به‌جای یک کوئری جدا برای هر کاربر). // برای فید دیسکاوری استفاده می‌شه که تا ۶۰ کاندیدا با هم پردازش می‌شن. func (r *OptionRepository) GetSelectionsForUsersInCategory(userIDs []uint, categoryID uint) (map[uint][]string, error) { result := make(map[uint][]string, len(userIDs)) if len(userIDs) == 0 { return result, nil } var selections []models.UserOptionSelection err := r.db.Preload("Option"). Where("user_id IN ? AND category_id = ?", userIDs, categoryID). Find(&selections).Error if err != nil { return nil, err } for _, sel := range selections { if sel.Option != nil { result[sel.UserID] = append(result[sel.UserID], sel.Option.Title) } } return result, nil } // GetUserSelectionsForCategories انتخاب‌های یک کاربر رو در چند دسته با هم، در // یک کوئری برمی‌گردونه (به‌جای یک کوئری جدا به‌ازای هر دسته). برای صفحه‌ی // جزئیات پروفایل استفاده می‌شه که هم‌زمان چند دسته (تحصیلات، شغل، علاقه‌مندی و...) // رو نشون می‌ده. func (r *OptionRepository) GetUserSelectionsForCategories(userID uint, categoryIDs []uint) (map[uint][]models.UserOptionSelection, error) { result := make(map[uint][]models.UserOptionSelection) if len(categoryIDs) == 0 { return result, nil } var selections []models.UserOptionSelection err := r.db.Preload("Option"). Where("user_id = ? AND category_id IN ?", userID, categoryIDs). Find(&selections).Error if err != nil { return nil, err } for _, sel := range selections { result[sel.CategoryID] = append(result[sel.CategoryID], sel) } return result, nil }