1dc96e86fc
- User blocking (with reason) now closes the auth gap immediately — a
blocked user's still-valid access token stops working on the next
request, not just on their next OTP/refresh.
- Admin-created reports (moderation notes) per user, viewable in a history
list independent of block status.
- GET /admin/users: full paginated, searchable user list with report counts.
- GET /admin/matches: all matches across the app ("friends" who mutually
liked each other), for admin visibility.
- GET/DELETE /admin/photos: paginated feed of every uploaded photo with
uploader info, so admins can review and remove content without going
user-by-user.
- Per-user activity: monitoring requests can now be filtered by user_id, so
a user's detail page can show their recent API activity.
- Batched the profile-selections query in fullProfilePayload (was 6 queries
per view, now 1) for consistency with the earlier N+1 fixes.
136 lines
4.6 KiB
Go
136 lines
4.6 KiB
Go
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
|
|
}
|