chore: merge backend repo history into monorepo at backend/
git-subtree-dir: backend git-subtree-mainline:dc11190527git-subtree-split:acae976622
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user