package database import ( "log" "time" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" "dating-app/internal/config" "dating-app/internal/models" ) // Connect به دیتابیس پستگرس وصل می‌شود func Connect(cfg *config.Config) (*gorm.DB, error) { logLevel := logger.Warn if cfg.AppEnv == "development" { logLevel = logger.Info } db, err := gorm.Open(postgres.Open(cfg.DSN()), &gorm.Config{ Logger: logger.Default.LogMode(logLevel), }) if err != nil { return nil, err } // تنظیم connection pool؛ بدون این، پیش‌فرض Go بی‌نهایت اتصال باز می‌کنه که // زیر بار بالا می‌تونه به‌راحتی خود Postgres رو با تعداد زیاد اتصال همزمان // از پا در بیاره. MaxIdleConns هم اتصالات راکد رو محدود می‌کنه تا حافظه‌ی // دیتابیس هدر نره. sqlDB, err := db.DB() if err != nil { return nil, err } sqlDB.SetMaxOpenConns(cfg.DBMaxOpenConns) sqlDB.SetMaxIdleConns(cfg.DBMaxIdleConns) sqlDB.SetConnMaxLifetime(time.Duration(cfg.DBConnMaxLifetimeMins) * time.Minute) sqlDB.SetConnMaxIdleTime(time.Duration(cfg.DBConnMaxIdleTimeMins) * time.Minute) return db, nil } // AutoMigrate جدول‌های مورد نیاز را می‌سازد/به‌روزرسانی می‌کند func AutoMigrate(db *gorm.DB) error { return db.AutoMigrate( &models.User{}, &models.Category{}, &models.Option{}, &models.UserOptionSelection{}, &models.ProfileQuestion{}, &models.UserProfileAnswer{}, &models.Photo{}, &models.Swipe{}, &models.Match{}, &models.Message{}, ) } // SeedCategories دسته‌های ثابت لیست‌های انتخابی را در صورت نبود می‌سازد. // خودِ گزینه‌های داخل هر دسته (به‌جز looking_for که صراحتاً در نیازمندی // پروژه مشخص شده) باید از پنل ادمین اضافه شوند. func SeedCategories(db *gorm.DB) error { defaults := []models.Category{ {Key: models.CategoryEducationLevel, Title: "سطح تحصیلات", MinSelect: 1, MaxSelect: 1}, {Key: models.CategoryFieldOfStudy, Title: "رشته تحصیلی", MinSelect: 1, MaxSelect: 1}, {Key: models.CategoryOccupation, Title: "کارهایی که انجام می‌دهید", MinSelect: 1, MaxSelect: 0}, {Key: models.CategoryImportantInRelationship, Title: "برات توی یک رابطه مهمه", MinSelect: 3, MaxSelect: 3}, {Key: models.CategoryInterest, Title: "علاقه‌مندی‌ها", MinSelect: 5, MaxSelect: 5}, {Key: models.CategoryLookingFor, Title: "دنبال چی می‌گردی", MinSelect: 1, MaxSelect: 1}, } for _, c := range defaults { var existing models.Category err := db.Where("key = ?", c.Key).First(&existing).Error if err == gorm.ErrRecordNotFound { if err := db.Create(&c).Error; err != nil { return err } log.Printf("دسته‌ی «%s» ساخته شد", c.Title) } else if err != nil { return err } } // گزینه‌های "دنبال چی می‌گردی" مطابق چیزی که در نیازمندی مشخص شده seed می‌شن var lookingFor models.Category if err := db.Where("key = ?", models.CategoryLookingFor).First(&lookingFor).Error; err == nil { var count int64 db.Model(&models.Option{}).Where("category_id = ?", lookingFor.ID).Count(&count) if count == 0 { titles := []string{ "رابطه بلندمدت", "همسر زندگی", "قرارهای تفریحی", "آشنایی با افراد جدید", "ازدواج", "دوست ساده", } for i, t := range titles { db.Create(&models.Option{ CategoryID: lookingFor.ID, Title: t, SortOrder: i, IsActive: true, }) } log.Println("گزینه‌های «دنبال چی می‌گردی» seed شدند") } } return nil } // BootstrapAdmin اگر شماره‌ای برای ادمین اولیه مشخص شده باشد، // یک کاربر ادمین با آن شماره می‌سازد (در صورت نبود). func BootstrapAdmin(db *gorm.DB, phone string) error { if phone == "" { return nil } var user models.User err := db.Where("phone = ?", phone).First(&user).Error if err == gorm.ErrRecordNotFound { user = models.User{ Phone: phone, State: models.UserStateActive, IsAdmin: true, IsActive: true, ApprovalStatus: models.ApprovalApproved, } if err := db.Create(&user).Error; err != nil { return err } log.Printf("کاربر ادمین اولیه با شماره %s ساخته شد", phone) return nil } else if err != nil { return err } if !user.IsAdmin { user.IsAdmin = true db.Save(&user) log.Printf("کاربر %s به ادمین ارتقا یافت", phone) } return nil }