package database import ( //"fmt" "log" "strings" "time" "gobeyhan/config" "gobeyhan/database/models" "golang.org/x/crypto/bcrypt" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" ) var DB *gorm.DB func ConnectDB() { dsn := config.AppConfig.DBUrl if dsn == "" { log.Fatal("DB_URL is not set in .env") } // Configure GORM with optimized settings db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logger.Info), // Only log errors, suppress SLOW SQL warnings PrepareStmt: true, // Prepare statements for better performance NowFunc: func() time.Time { return time.Now().UTC() }, }) if err != nil { log.Fatal("Failed to connect to database:", err) } log.Println("Connected to Database successfully") DB = db // MySQL doesn't require enabling uuid-ossp extension. noop for compatibility onEnableUUIDForMySQL() } func onEnableUUIDForMySQL() { // noop: Postgres-only extension; for MySQL UUID handling is usually done at application level log.Println("UUID extension step skipped for MySQL (not required)") } func SeedAll() { if DB == nil { log.Println("DB not initialized: call ConnectDB() before SeedAll") return } // Run AutoMigrate using the helper in migrate.go if err := Migrate(DB); err != nil { log.Printf("AutoMigrate failed: %v", err) return } // Run schema/data migrations migrateUserNameColumn() migrateEmailVerifiedColumn() // Seed initial data seedRolesAndPermissions() seedDefaultSettings() SeedDefaultAdmin() log.Println("Database migration and seeding complete") } func migrateEmailVerifiedColumn() { // Check column existence via information_schema for MySQL var count int64 DB.Raw(` SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'email_verified' `).Scan(&count) if count == 0 { // Column doesn't exist, nothing to migrate return } // Only set existing users (created before email verification feature) as verified var usersToVerify int64 DB.Model(&models.User{}).Where("(email_verify_token IS NULL OR email_verify_token = '') AND email_verified IS NULL").Count(&usersToVerify) if usersToVerify > 0 { DB.Exec("UPDATE users SET email_verified = true WHERE (email_verify_token IS NULL OR email_verify_token = '') AND email_verified IS NULL") log.Printf("Email verification migration: %d existing users marked as verified", usersToVerify) } } func migrateUserNameColumn() { // Check column existence via information_schema for MySQL var count int64 DB.Raw(` SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'user_name' `).Scan(&count) if count == 0 { // Column doesn't exist, add it log.Println("Adding user_name column...") DB.Exec("ALTER TABLE users ADD COLUMN user_name TEXT") // Update existing users with default usernames DB.Exec("UPDATE users SET user_name = CONCAT('user_', SUBSTRING(CAST(id AS CHAR), 1, 8)) WHERE user_name IS NULL") // Add NOT NULL constraint DB.Exec("ALTER TABLE users MODIFY COLUMN user_name TEXT NOT NULL") log.Println("user_name column added successfully") return } // Column exists, update null or empty values log.Println("Updating users with null or empty usernames...") DB.Exec("UPDATE users SET user_name = CONCAT('user_', SUBSTRING(CAST(id AS CHAR), 1, 8)) WHERE user_name IS NULL OR user_name = ''") // Check if NOT NULL constraint exists using information_schema var isNullable string DB.Raw(` SELECT IS_NULLABLE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'user_name' `).Scan(&isNullable) if strings.ToUpper(isNullable) != "NO" { log.Println("Adding NOT NULL constraint to user_name...") DB.Exec("ALTER TABLE users MODIFY COLUMN user_name TEXT NOT NULL") } } func seedRolesAndPermissions() { // 1. Define Permissions permissions := []models.Permission{ {Name: "user:read", Description: "Can read user data"}, {Name: "user:write", Description: "Can modify user data"}, {Name: "admin:access", Description: "Can access admin panel"}, } for _, p := range permissions { DB.FirstOrCreate(&models.Permission{}, models.Permission{Name: p.Name, Description: p.Description}) } // 2. Define Roles roles := []string{"admin", "user"} for _, r := range roles { DB.FirstOrCreate(&models.Role{}, models.Role{Name: r, Description: "Default " + r + " role"}) } // 3. Assign Permissions to Admin Role var adminRole models.Role DB.Preload("Permissions").Where("name = ?", "admin").First(&adminRole) // Fetch all permissions to assign to admin var allPermissions []models.Permission DB.Find(&allPermissions) // Update association (replace current set) DB.Model(&adminRole).Association("Permissions").Replace(allPermissions) // 4. Assign Basic Permissions to User Role var userRole models.Role DB.Preload("Permissions").Where("name = ?", "user").First(&userRole) var userPermissions []models.Permission DB.Where("name IN ?", []string{"user:read"}).Find(&userPermissions) DB.Model(&userRole).Association("Permissions").Replace(userPermissions) log.Println("Roles and Permissions seeded") } func seedDefaultSettings() { // Seed default CORS whitelist var whitelistCount int64 DB.Model(&models.CorsWhitelist{}).Count(&whitelistCount) if whitelistCount == 0 { defaultWhitelist := []models.CorsWhitelist{ { Origin: "http://localhost:3000", Description: "Default local frontend", IsActive: true, CreatedBy: "system", }, { Origin: "http://localhost:8080", Description: "Backend self", IsActive: true, CreatedBy: "system", }, } for _, w := range defaultWhitelist { DB.Create(&w) } log.Println("Default CORS whitelist seeded") } // Seed default rate limit settings var rateLimitCount int64 DB.Model(&models.RateLimitSetting{}).Count(&rateLimitCount) if rateLimitCount == 0 { defaultRateLimits := []models.RateLimitSetting{ { Name: "login", Description: "Login endpoint rate limit", MaxRequests: 5, WindowSeconds: 60, // 1 minute IsActive: true, }, { Name: "register", Description: "Registration endpoint rate limit", MaxRequests: 3, WindowSeconds: 300, // 5 minutes IsActive: true, }, { Name: "api", Description: "General API rate limit", MaxRequests: 100, WindowSeconds: 60, // 1 minute IsActive: true, }, } for _, r := range defaultRateLimits { DB.Create(&r) } log.Println("Default rate limit settings seeded") } } // SeedDefaultAdmin creates the default admin user if it doesn't exist func SeedDefaultAdmin() { if DB == nil { log.Println("DB not initialized: call ConnectDB() before seeding") return } // Use a transaction to ensure atomic create + role assignment tx := DB.Begin() defer func() { if r := recover(); r != nil { tx.Rollback() log.Printf("panic during SeedDefaultAdmin: %v", r) } }() // Check if admin user already exists (including soft-deleted) var adminUser models.User err := tx.Unscoped().Where("email = ?", "admin@gauth.local").First(&adminUser).Error if err != nil { // Admin user doesn't exist, create one // Hash default password: "Admin@123" hashedPassword, err := bcrypt.GenerateFromPassword([]byte("Admin@123"), bcrypt.DefaultCost) if err != nil { log.Printf("Failed to hash admin password: %v", err) tx.Rollback() return } trueBool := true adminUser = models.User{ Email: "admin@gauth.local", UserName: "admin", Password: string(hashedPassword), EmailVerified: &trueBool, } if err := tx.Create(&adminUser).Error; err != nil { log.Printf("Failed to create admin user: %v", err) tx.Rollback() return } // Log created admin ID and type for debugging log.Printf("Admin created - ID value: %v (type: %T)", adminUser.ID, adminUser.ID) log.Println("✅ Default admin user created:") log.Println(" Email: admin@gauth.local") log.Println(" Password: Admin@123") log.Println(" ⚠️ Please change this password after first login!") } else { // Admin user exists (possibly soft-deleted) if adminUser.DeletedAt.Valid { log.Println("Restoring deleted admin user...") if err := tx.Model(&adminUser).Unscoped().Update("deleted_at", nil).Error; err != nil { log.Printf("Failed to restore admin user: %v", err) tx.Rollback() return } } // Log existing admin ID for debugging log.Printf("Admin already exists - ID value: %v (type: %T)", adminUser.ID, adminUser.ID) } // Ensure admin role is assigned var adminRole models.Role if err := tx.Where("name = ?", "admin").First(&adminRole).Error; err != nil { log.Printf("Admin role not found: %v", err) tx.Rollback() return } if err := tx.Model(&adminUser).Association("Roles").Append(&adminRole); err != nil { log.Printf("Failed to assign admin role: %v", err) tx.Rollback() return } if err := tx.Commit().Error; err != nil { log.Printf("Failed to commit admin seed transaction: %v", err) return } log.Println("Varsayılan Yönetici Yaratıldı...") }