chore: merge backend repo history into monorepo at backend/

git-subtree-dir: backend
git-subtree-mainline: dc11190527
git-subtree-split: acae976622
This commit is contained in:
2026-09-27 10:30:16 +03:30
67 changed files with 6835 additions and 0 deletions
@@ -0,0 +1,88 @@
package repository
import (
"errors"
"gorm.io/gorm"
"dating-app/internal/models"
)
type MatchRepository struct {
db *gorm.DB
}
func NewMatchRepository(db *gorm.DB) *MatchRepository {
return &MatchRepository{db: db}
}
// normalizePair همیشه شناسه‌ی کوچیک‌تر رو اول می‌ذاره تا هر جفت فقط یک رکورد داشته باشه
func normalizePair(a, b uint) (uint, uint) {
if a < b {
return a, b
}
return b, a
}
// FindOrCreate اگه مچ بین این دو کاربر از قبل وجود داشته باشه همونو برمی‌گردونه،
// وگرنه می‌سازتش. خروجی دوم true یعنی همین الان تازه ساخته شده.
func (r *MatchRepository) FindOrCreate(userA, userB uint) (*models.Match, bool, error) {
one, two := normalizePair(userA, userB)
var match models.Match
err := r.db.Where("user_one_id = ? AND user_two_id = ?", one, two).First(&match).Error
if err == nil {
return &match, false, nil
}
if !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, false, err
}
match = models.Match{UserOneID: one, UserTwoID: two}
if err := r.db.Create(&match).Error; err != nil {
return nil, false, err
}
return &match, true, nil
}
func (r *MatchRepository) FindByID(id uint) (*models.Match, error) {
var match models.Match
if err := r.db.First(&match, id).Error; err != nil {
return nil, err
}
return &match, nil
}
// ListByUser مچ‌های کاربر رو از جدیدترین به قدیمی‌ترین برمی‌گردونه
func (r *MatchRepository) ListByUser(userID uint) ([]models.Match, error) {
var matches []models.Match
err := r.db.
Where("user_one_id = ? OR user_two_id = ?", userID, userID).
Order("created_at desc").
Find(&matches).Error
return matches, err
}
// ListAllPaginated همه‌ی مچ‌های اپ رو صفحه‌بندی‌شده برمی‌گردونه (برای پنل ادمین)
func (r *MatchRepository) ListAllPaginated(limit, offset int) ([]models.Match, int64, error) {
var total int64
if err := r.db.Model(&models.Match{}).Count(&total).Error; err != nil {
return nil, 0, err
}
var matches []models.Match
err := r.db.Order("created_at desc").Limit(limit).Offset(offset).Find(&matches).Error
return matches, total, err
}
// OtherUserID شناسه‌ی طرف مقابل کاربر جاری رو توی یک مچ برمی‌گردونه
func OtherUserID(match *models.Match, currentUserID uint) uint {
if match.UserOneID == currentUserID {
return match.UserTwoID
}
return match.UserOneID
}
// IsMember یعنی این کاربر عضو این مچه
func IsMember(match *models.Match, userID uint) bool {
return match.UserOneID == userID || match.UserTwoID == userID
}
@@ -0,0 +1,61 @@
package repository
import (
"gorm.io/gorm"
"dating-app/internal/models"
)
type MessageRepository struct {
db *gorm.DB
}
func NewMessageRepository(db *gorm.DB) *MessageRepository {
return &MessageRepository{db: db}
}
func (r *MessageRepository) Create(msg *models.Message) error {
return r.db.Create(msg).Error
}
func (r *MessageRepository) ListByMatch(matchID uint) ([]models.Message, error) {
var messages []models.Message
err := r.db.Where("match_id = ?", matchID).Order("created_at asc").Find(&messages).Error
return messages, err
}
// LastMessage آخرین پیام یک مچ رو برمی‌گردونه (برای پیش‌نمایش لیست گفتگوها)
func (r *MessageRepository) LastMessage(matchID uint) (*models.Message, error) {
var msg models.Message
err := r.db.Where("match_id = ?", matchID).Order("created_at desc").First(&msg).Error
if err != nil {
return nil, err
}
return &msg, nil
}
// LastMessagesByMatchIDs آخرین پیام هر مچ رو، برای چند مچ با هم در یک کوئری
// برمی‌گردونه (به‌جای یک کوئری جدا به‌ازای هر مچ توی لیست گفتگوها). از
// DISTINCT ON پستگرس استفاده می‌کنه که دقیقاً برای همین کار («آخرین ردیف هر
// گروه») ساخته شده.
func (r *MessageRepository) LastMessagesByMatchIDs(matchIDs []uint) (map[uint]models.Message, error) {
result := make(map[uint]models.Message, len(matchIDs))
if len(matchIDs) == 0 {
return result, nil
}
var messages []models.Message
err := r.db.
Distinct("ON (match_id) *").
Where("match_id IN ?", matchIDs).
Order("match_id, created_at desc").
Find(&messages).Error
if err != nil {
return nil, err
}
for _, m := range messages {
result[m.MatchID] = m
}
return result, nil
}
@@ -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
}
@@ -0,0 +1,64 @@
package repository
import (
"gorm.io/gorm"
"dating-app/internal/models"
)
type PhotoRepository struct {
db *gorm.DB
}
func NewPhotoRepository(db *gorm.DB) *PhotoRepository {
return &PhotoRepository{db: db}
}
func (r *PhotoRepository) ListByUser(userID uint) ([]models.Photo, error) {
var photos []models.Photo
err := r.db.Where("user_id = ?", userID).Order("sort_order asc, id asc").Find(&photos).Error
return photos, err
}
func (r *PhotoRepository) ListByUserIDs(userIDs []uint) ([]models.Photo, error) {
var photos []models.Photo
if len(userIDs) == 0 {
return photos, nil
}
err := r.db.Where("user_id IN ?", userIDs).Order("sort_order asc, id asc").Find(&photos).Error
return photos, err
}
// ListAllPaginated همه‌ی عکس‌های آپلودشده‌ی اپ رو صفحه‌بندی‌شده، جدیدترین اول
// برمی‌گردونه (برای نظارت محتوایی پنل ادمین).
func (r *PhotoRepository) ListAllPaginated(limit, offset int) ([]models.Photo, int64, error) {
var total int64
if err := r.db.Model(&models.Photo{}).Count(&total).Error; err != nil {
return nil, 0, err
}
var photos []models.Photo
err := r.db.Order("created_at desc").Limit(limit).Offset(offset).Find(&photos).Error
return photos, total, err
}
func (r *PhotoRepository) CountByUser(userID uint) (int64, error) {
var count int64
err := r.db.Model(&models.Photo{}).Where("user_id = ?", userID).Count(&count).Error
return count, err
}
func (r *PhotoRepository) Create(photo *models.Photo) error {
return r.db.Create(photo).Error
}
func (r *PhotoRepository) FindByID(id uint) (*models.Photo, error) {
var photo models.Photo
if err := r.db.First(&photo, id).Error; err != nil {
return nil, err
}
return &photo, nil
}
func (r *PhotoRepository) Delete(id uint) error {
return r.db.Delete(&models.Photo{}, id).Error
}
@@ -0,0 +1,111 @@
package repository
import (
"time"
"gorm.io/gorm"
"dating-app/internal/cache"
"dating-app/internal/models"
)
const activeQuestionsCacheTTL = 5 * time.Minute
const activeQuestionsCacheKey = "active"
type QuestionRepository struct {
db *gorm.DB
activeCache *cache.TTLCache[string, []models.ProfileQuestion]
}
func NewQuestionRepository(db *gorm.DB) *QuestionRepository {
return &QuestionRepository{
db: db,
activeCache: cache.New[string, []models.ProfileQuestion](activeQuestionsCacheTTL),
}
}
func (r *QuestionRepository) ListActive() ([]models.ProfileQuestion, error) {
if cached, ok := r.activeCache.Get(activeQuestionsCacheKey); ok {
return cached, nil
}
var qs []models.ProfileQuestion
err := r.db.Where("is_active = ?", true).Order("sort_order asc, id asc").Find(&qs).Error
if err != nil {
return nil, err
}
r.activeCache.Set(activeQuestionsCacheKey, qs)
return qs, nil
}
func (r *QuestionRepository) ListAllAdmin() ([]models.ProfileQuestion, error) {
var qs []models.ProfileQuestion
err := r.db.Order("sort_order asc, id asc").Find(&qs).Error
return qs, err
}
func (r *QuestionRepository) FindByID(id uint) (*models.ProfileQuestion, error) {
var q models.ProfileQuestion
err := r.db.First(&q, id).Error
if err != nil {
return nil, err
}
return &q, nil
}
func (r *QuestionRepository) FindActiveByIDs(ids []uint) ([]models.ProfileQuestion, error) {
var qs []models.ProfileQuestion
err := r.db.Where("is_active = ? AND id IN ?", true, ids).Find(&qs).Error
return qs, err
}
func (r *QuestionRepository) Create(q *models.ProfileQuestion) error {
if err := r.db.Create(q).Error; err != nil {
return err
}
r.activeCache.Clear()
return nil
}
func (r *QuestionRepository) Update(q *models.ProfileQuestion) error {
if err := r.db.Save(q).Error; err != nil {
return err
}
r.activeCache.Clear()
return nil
}
func (r *QuestionRepository) Delete(id uint) error {
if err := r.db.Delete(&models.ProfileQuestion{}, id).Error; err != nil {
return err
}
r.activeCache.Clear()
return nil
}
// ReplaceUserAnswers پاسخ‌های قبلی کاربر رو با پاسخ‌های جدید جایگزین می‌کنه (تراکنشی)
func (r *QuestionRepository) ReplaceUserAnswers(userID uint, answers []models.UserProfileAnswer) error {
return r.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Where("user_id = ?", userID).Delete(&models.UserProfileAnswer{}).Error; err != nil {
return err
}
for i := range answers {
answers[i].UserID = userID
if err := tx.Create(&answers[i]).Error; err != nil {
return err
}
}
return nil
})
}
func (r *QuestionRepository) GetUserAnswers(userID uint) ([]models.UserProfileAnswer, error) {
var answers []models.UserProfileAnswer
err := r.db.Preload("Question").Where("user_id = ?", userID).Find(&answers).Error
return answers, err
}
func (r *QuestionRepository) CountUserAnswers(userID uint) (int64, error) {
var count int64
err := r.db.Model(&models.UserProfileAnswer{}).Where("user_id = ?", userID).Count(&count).Error
return count, err
}
@@ -0,0 +1,53 @@
package repository
import (
"gorm.io/gorm"
"dating-app/internal/models"
)
type ReportRepository struct {
db *gorm.DB
}
func NewReportRepository(db *gorm.DB) *ReportRepository {
return &ReportRepository{db: db}
}
func (r *ReportRepository) Create(report *models.Report) error {
return r.db.Create(report).Error
}
func (r *ReportRepository) ListByUser(userID uint) ([]models.Report, error) {
var reports []models.Report
err := r.db.Where("user_id = ?", userID).Order("created_at desc").Find(&reports).Error
return reports, err
}
// CountsByUserIDs تعداد گزارش‌های ثبت‌شده روی چند کاربر رو با هم، در یک
// کوئری برمی‌گردونه (برای نشون دادن نشان تعداد گزارش روی لیست کاربران، بدون
// یک کوئری جدا به‌ازای هر کاربر).
func (r *ReportRepository) CountsByUserIDs(userIDs []uint) (map[uint]int64, error) {
result := make(map[uint]int64, len(userIDs))
if len(userIDs) == 0 {
return result, nil
}
type row struct {
UserID uint
Count int64
}
var rows []row
err := r.db.Model(&models.Report{}).
Select("user_id, count(*) as count").
Where("user_id IN ?", userIDs).
Group("user_id").
Find(&rows).Error
if err != nil {
return nil, err
}
for _, rr := range rows {
result[rr.UserID] = rr.Count
}
return result, nil
}
@@ -0,0 +1,42 @@
package repository
import (
"errors"
"gorm.io/gorm"
"dating-app/internal/models"
)
type SwipeRepository struct {
db *gorm.DB
}
func NewSwipeRepository(db *gorm.DB) *SwipeRepository {
return &SwipeRepository{db: db}
}
func (r *SwipeRepository) Create(swipe *models.Swipe) error {
return r.db.Create(swipe).Error
}
// HasLiked یعنی swiperID قبلاً swipedID رو لایک/سوپرلایک کرده
func (r *SwipeRepository) HasLiked(swiperID, swipedID uint) (bool, error) {
var swipe models.Swipe
err := r.db.
Where("swiper_id = ? AND swiped_id = ? AND action IN ?", swiperID, swipedID, []models.SwipeAction{models.SwipeLike, models.SwipeSuperlike}).
First(&swipe).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return false, nil
}
if err != nil {
return false, err
}
return true, nil
}
func (r *SwipeRepository) Exists(swiperID, swipedID uint) (bool, error) {
var count int64
err := r.db.Model(&models.Swipe{}).Where("swiper_id = ? AND swiped_id = ?", swiperID, swipedID).Count(&count).Error
return count > 0, err
}
@@ -0,0 +1,135 @@
package repository
import (
"time"
"gorm.io/gorm"
"dating-app/internal/models"
)
type UserRepository struct {
db *gorm.DB
}
func NewUserRepository(db *gorm.DB) *UserRepository {
return &UserRepository{db: db}
}
func (r *UserRepository) FindByPhone(phone string) (*models.User, error) {
var user models.User
err := r.db.Where("phone = ?", phone).First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}
func (r *UserRepository) FindByID(id uint) (*models.User, error) {
var user models.User
err := r.db.First(&user, id).Error
if err != nil {
return nil, err
}
return &user, nil
}
// FindByIDs چند کاربر رو با هم در یک کوئری برمی‌گردونه (به‌جای یک FindByID
// جدا به‌ازای هر id)؛ برای لیست‌هایی مثل مچ‌ها که باید اطلاعات طرف مقابل
// چندین مچ رو یک‌جا نشون بدن.
func (r *UserRepository) FindByIDs(ids []uint) ([]models.User, error) {
var users []models.User
if len(ids) == 0 {
return users, nil
}
err := r.db.Where("id IN ?", ids).Find(&users).Error
return users, err
}
// GetOrCreateByPhone کاربر را با شماره‌ی داده‌شده پیدا می‌کند یا در صورت نبود می‌سازد.
func (r *UserRepository) GetOrCreateByPhone(phone string) (*models.User, bool, error) {
var user models.User
err := r.db.Where("phone = ?", phone).First(&user).Error
if err == nil {
return &user, false, nil
}
if err != gorm.ErrRecordNotFound {
return nil, false, err
}
user = models.User{
Phone: phone,
State: models.UserStateInactive,
IsActive: true,
CurrentStep: models.StepBasicInfo,
}
if err := r.db.Create(&user).Error; err != nil {
return nil, false, err
}
return &user, true, nil
}
// ListPendingApproval کاربرانی که آنبوردینگ رو تکمیل کردن و منتظر بررسی ادمین‌اند
// رو به ترتیب قدیمی‌ترین اول برمی‌گردونه.
func (r *UserRepository) ListPendingApproval() ([]models.User, error) {
var users []models.User
err := r.db.
Where("is_onboarded = ? AND approval_status = ?", true, models.ApprovalPending).
Order("updated_at asc").
Find(&users).Error
return users, err
}
// ListAll همه‌ی کاربران رو صفحه‌بندی‌شده برمی‌گردونه (برای صفحه‌ی «همه‌ی
// کاربران» پنل ادمین)؛ search روی شماره موبایل و اسم کوچیک می‌گرده.
func (r *UserRepository) ListAll(search string, limit, offset int) ([]models.User, int64, error) {
query := r.db.Model(&models.User{})
if search != "" {
like := "%" + search + "%"
query = query.Where("phone ILIKE ? OR first_name ILIKE ?", like, like)
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
var users []models.User
err := query.Order("created_at desc").Limit(limit).Offset(offset).Find(&users).Error
return users, total, err
}
// ListDiscoverable کاندیداهای بخش دوستیابی رو برمی‌گردونه: تاییدشده، فعال،
// جنسیت مخالف کاربر (به‌عنوان ساده‌ترین فرض پیش‌فرض؛ فیلد ترجیح جنسیتی/گرایش
// هنوز توی سیستم وجود نداره) و کسایی که قبلاً روشون سواپ نزده.
// چون فاصله باید توی Go محاسبه بشه (haversine)، یک batch بزرگ‌تر از حد
// نهایی برمی‌گردونه تا بعد از فیلتر فاصله همچنان گزینه‌ی کافی بمونه.
func (r *UserRepository) ListDiscoverable(currentUserID uint, oppositeGender models.Gender, minAge, maxAge int, batchSize int) ([]models.User, error) {
var users []models.User
now := time.Now()
minBirth := now.AddDate(-maxAge-1, 0, 1)
maxBirth := now.AddDate(-minAge, 0, 0)
err := r.db.
Where("id != ?", currentUserID).
Where("is_onboarded = ? AND approval_status = ? AND is_active = ?", true, models.ApprovalApproved, true).
Where("gender = ?", oppositeGender).
Where("birth_date BETWEEN ? AND ?", minBirth, maxBirth).
Where("NOT EXISTS (SELECT 1 FROM swipes s WHERE s.swiper_id = ? AND s.swiped_id = users.id)", currentUserID).
Order("id asc").
Limit(batchSize).
Find(&users).Error
return users, err
}
func (r *UserRepository) Save(user *models.User) error {
return r.db.Save(user).Error
}
func (r *UserRepository) UpdateFields(user *models.User, fields map[string]interface{}) error {
return r.db.Model(user).Updates(fields).Error
}
func (r *UserRepository) DB() *gorm.DB {
return r.db
}