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,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
|
||||
}
|
||||
Reference in New Issue
Block a user