cab-backend/internal/migrations/migrate.go
2026-03-30 21:00:35 +03:00

146 lines
5.4 KiB
Go

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
}