package migrations import ( "context" "fmt" "time" "game-admin/internal/config" "game-admin/internal/models" "github.com/uptrace/bun" "golang.org/x/crypto/bcrypt" ) func Migrate(ctx context.Context, db *bun.DB, cfg *config.Config) error { tables := []interface{}{ (*models.User)(nil), (*models.Game)(nil), (*models.Language)(nil), (*models.Module)(nil), (*models.GameModule)(nil), (*models.GameVariable)(nil), (*models.GameVariableItem)(nil), (*models.Notification)(nil), (*models.NotificationDescription)(nil), (*models.NotificationVariableDef)(nil), (*models.NotificationEntry)(nil), (*models.NotificationEntryVariable)(nil), (*models.Leaderboard)(nil), (*models.LeaderboardGroup)(nil), (*models.LeaderboardGroupMember)(nil), (*models.LeaderboardScore)(nil), (*models.UserGame)(nil), (*models.InviteLink)(nil), (*models.BalanceTransaction)(nil), (*models.Transaction)(nil), } for _, model := range tables { _, err := db.NewCreateTable(). Model(model). IfNotExists(). Exec(ctx) if err != nil { return fmt.Errorf("create table: %w", err) } } _, _ = db.ExecContext(ctx, "CREATE UNIQUE INDEX IF NOT EXISTS idx_user_games_unique ON user_games (user_id, game_id)") _, _ = db.ExecContext(ctx, `CREATE UNIQUE INDEX IF NOT EXISTS idx_languages_code_unique ON languages ("code")`) _, _ = db.ExecContext(ctx, `CREATE UNIQUE INDEX IF NOT EXISTS idx_modules_key_unique ON modules ("key")`) _, _ = db.ExecContext(ctx, "CREATE UNIQUE INDEX IF NOT EXISTS idx_game_modules_unique ON game_modules (game_id, module_id)") _, _ = db.ExecContext(ctx, `CREATE UNIQUE INDEX IF NOT EXISTS idx_game_variables_unique ON game_variables (game_id, "key")`) _, _ = db.ExecContext(ctx, "CREATE UNIQUE INDEX IF NOT EXISTS idx_game_variable_items_unique ON game_variable_items (game_variable_id, item_key)") _, _ = db.ExecContext(ctx, "CREATE UNIQUE INDEX IF NOT EXISTS idx_notifications_unique ON notifications (game_id, name)") _, _ = db.ExecContext(ctx, "CREATE UNIQUE INDEX IF NOT EXISTS idx_notification_descriptions_unique ON notification_descriptions (notification_id, language_id)") _, _ = db.ExecContext(ctx, `CREATE UNIQUE INDEX IF NOT EXISTS idx_notification_variable_defs_unique ON notification_variable_defs (notification_id, "key")`) _, _ = db.ExecContext(ctx, "CREATE UNIQUE INDEX IF NOT EXISTS idx_notification_entry_variables_unique ON notification_entry_variables (notification_entry_id, notification_variable_id)") _, _ = db.ExecContext(ctx, `CREATE UNIQUE INDEX IF NOT EXISTS idx_leaderboards_unique ON leaderboards (game_id, "key")`) _, _ = db.ExecContext(ctx, `CREATE UNIQUE INDEX IF NOT EXISTS idx_leaderboard_groups_unique ON leaderboard_groups (leaderboard_id, "key")`) _, _ = db.ExecContext(ctx, "CREATE UNIQUE INDEX IF NOT EXISTS idx_leaderboard_group_members_unique ON leaderboard_group_members (group_id, user_id)") _, _ = db.ExecContext(ctx, "CREATE UNIQUE INDEX IF NOT EXISTS idx_leaderboard_scores_unique ON leaderboard_scores (leaderboard_id, user_game_id)") _, _ = db.ExecContext(ctx, "CREATE UNIQUE INDEX IF NOT EXISTS idx_transactions_request_id_unique ON transactions (request_id)") _, _ = db.ExecContext(ctx, "CREATE INDEX IF NOT EXISTS idx_transactions_user_game_status ON transactions (user_id, game_id, status)") // Seed a default admin only when there are no users yet. userCount, err := db.NewSelect().Model((*models.User)(nil)).Count(ctx) if err != nil { return fmt.Errorf("count users: %w", err) } if userCount == 0 { hashed, err := bcrypt.GenerateFromPassword([]byte(cfg.DefaultAdminPassword), bcrypt.DefaultCost) if err != nil { return fmt.Errorf("hash default admin password: %w", err) } admin := &models.User{ Email: cfg.DefaultAdminEmail, Username: cfg.DefaultAdminUsername, Password: string(hashed), Role: models.RoleAdmin, IsActive: true, CreatedAt: time.Now(), UpdatedAt: time.Now(), } _, err = db.NewInsert().Model(admin).Exec(ctx) if err != nil { return fmt.Errorf("seed default admin: %w", err) } fmt.Printf("default admin user seeded: %s / %s\n", cfg.DefaultAdminEmail, cfg.DefaultAdminPassword) } games := []models.Game{ {Name: "Black & White", Slug: "b&d", Description: "Sandbox", IsActive: true, CreatedAt: time.Now(), UpdatedAt: time.Now()}, } for _, g := range games { exists, _ := db.NewSelect().Model((*models.Game)(nil)).Where("slug = ?", g.Slug).Exists(ctx) if !exists { _, _ = db.NewInsert().Model(&g).Exec(ctx) } } defaultModules := []models.Module{ {Key: "variables", Name: "Variables", CreatedAt: time.Now(), UpdatedAt: time.Now()}, {Key: "notification", Name: "Notification", CreatedAt: time.Now(), UpdatedAt: time.Now()}, {Key: "leaderboard", Name: "Leaderboard", CreatedAt: time.Now(), UpdatedAt: time.Now()}, } for _, module := range defaultModules { exists, _ := db.NewSelect().Model((*models.Module)(nil)).Where(`"key" = ?`, module.Key).Exists(ctx) if !exists { _, _ = db.NewInsert().Model(&module).Exec(ctx) } } defaultLanguages := []models.Language{ {Code: "en", Name: "English", CreatedAt: time.Now(), UpdatedAt: time.Now()}, {Code: "ru", Name: "Русский", CreatedAt: time.Now(), UpdatedAt: time.Now()}, } for _, language := range defaultLanguages { exists, _ := db.NewSelect().Model((*models.Language)(nil)).Where("code = ?", language.Code).Exists(ctx) if !exists { _, _ = db.NewInsert().Model(&language).Exec(ctx) } } fmt.Println("migrations complete") return nil }