diff --git a/.env.example b/.env.example index b131541..d77d3c1 100644 --- a/.env.example +++ b/.env.example @@ -1 +1 @@ -DATABASE_URL=postgresql://postgres:password@localhost:5432/db +PG_FROM_DB_URL=postgresql://postgres:password@localhost:5432/db diff --git a/main.go b/main.go index a06ca6e..49262d6 100644 --- a/main.go +++ b/main.go @@ -16,7 +16,7 @@ func main() { log.Fatal("Error al cargar el archivo .env") } - dbpool, err := pgxpool.New(context.Background(), os.Getenv("DATABASE_URL")) + dbpool, err := pgxpool.New(context.Background(), os.Getenv("PG_FROM_DB_URL")) if err != nil { fmt.Fprintf(os.Stderr, "Unable to create connection pool: %v\n", err) os.Exit(1) diff --git a/scripts/fixtures/create_users_fixture.go b/scripts/fixtures/create_users_fixture.go new file mode 100644 index 0000000..c175e00 --- /dev/null +++ b/scripts/fixtures/create_users_fixture.go @@ -0,0 +1,261 @@ +package main + +import ( + "context" + "fmt" + "log" + "math" + "math/rand" + "os" + "time" + + "github.com/jackc/pgx/v5/pgxpool" + "github.com/joho/godotenv" +) + +var ( + firstNames = []string{ + "Juan", "María", "Carlos", "Ana", "Miguel", "Rosa", "Pedro", "Isabel", + "Luis", "Carmen", "José", "Antonia", "Fernando", "Dolores", "Diego", "Margarita", + "Alejandro", "Francisca", "Antonio", "Juana", "Francisco", "Pilar", "Andrés", "Elena", + "Rafael", "Teresa", "Enrique", "Ángeles", "Jorge", "Soledad", "Ramón", "Asunción", + } + lastNames = []string{ + "García", "Rodríguez", "González", "López", "Martínez", "Sánchez", "Peña", "Castellanos", + "Moreno", "Jiménez", "Hernández", "Díaz", "Ramírez", "Cruz", "Velasco", "Campos", + "Medina", "Ruiz", "Domínguez", "Delgado", "Flores", "Silver", "Torres", "Rivera", + "Vargas", "Castro", "Vega", "Rojas", "Parra", "Salazar", "Guzmán", "Ochoa", + } + domainNames = []string{ + "gmail.com", "yahoo.com", "hotmail.com", "outlook.com", "empresa.com", + "mail.com", "correo.es", "example.com", "test.io", "domain.dev", + } +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +func randomFirstName() string { + return firstNames[rand.Intn(len(firstNames))] +} + +func randomLastName() string { + return lastNames[rand.Intn(len(lastNames))] +} + +func randomEmail() string { + firstName := randomFirstName() + lastName := randomLastName() + domain := domainNames[rand.Intn(len(domainNames))] + return fmt.Sprintf("%s.%s@%s", firstName, lastName, domain) +} + +func randomAge() int { + return rand.Intn(70) + 18 +} + +func randomSalary() float64 { + return math.Round((rand.Float64()*100000 + 25000)) / 100 +} + +func randomBirthDate() time.Time { + years := rand.Intn(70) + 18 + days := rand.Intn(365) + return time.Now().AddDate(-years, 0, -days) +} + +func randomPhoneNumber() string { + return fmt.Sprintf("+34 6%d %d%d%d%d%d%d%d", + rand.Intn(10), + rand.Intn(10), rand.Intn(10), rand.Intn(10), rand.Intn(10), + rand.Intn(10), rand.Intn(10), rand.Intn(10), + ) +} + +func randomCity() string { + cities := []string{ + "Madrid", "Barcelona", "Valencia", "Sevilla", "Bilbao", + "Zaragoza", "Málaga", "Murcia", "Palma", "Las Palmas", + "Córdoba", "Valladolid", "Vigo", "Gijón", "Hospitalet", + } + return cities[rand.Intn(len(cities))] +} + +func randomHobbies() []string { + allHobbies := []string{ + "lectura", "viajes", "cine", "música", "deportes", + "cocina", "fotografía", "gaming", "yoga", "senderismo", + "natación", "tenis", "ajedrez", "pintura", "jardinería", + } + + numHobbies := rand.Intn(4) + 1 + selected := make([]string, 0, numHobbies) + used := make(map[int]bool) + + for len(selected) < numHobbies { + idx := rand.Intn(len(allHobbies)) + if !used[idx] { + used[idx] = true + selected = append(selected, allHobbies[idx]) + } + } + return selected +} + +func createUsersTable(db *pgxpool.Pool, ctx context.Context) error { + createTableSQL := ` + CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + first_name VARCHAR(100) NOT NULL, + last_name VARCHAR(100) NOT NULL, + email VARCHAR(255) UNIQUE NOT NULL, + age INTEGER NOT NULL CHECK (age >= 18), + birth_date DATE NOT NULL, + salary NUMERIC(12, 2), + phone_number VARCHAR(20), + is_active BOOLEAN DEFAULT true, + is_verified BOOLEAN DEFAULT false, + city VARCHAR(100), + hobbies TEXT[], + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + metadata JSONB + ); + ` + + _, err := db.Exec(ctx, createTableSQL) + if err != nil { + return fmt.Errorf("error creating users table: %w", err) + } + + return nil +} + +func truncateUsersTable(db *pgxpool.Pool, ctx context.Context) error { + truncateSQL := `TRUNCATE TABLE users RESTART IDENTITY CASCADE;` + _, err := db.Exec(ctx, truncateSQL) + if err != nil { + return fmt.Errorf("error truncating users table: %w", err) + } + return nil +} + +func insertUserRecord(db *pgxpool.Pool, ctx context.Context, userNum int) error { + firstName := randomFirstName() + lastName := randomLastName() + email := randomEmail() + age := randomAge() + birthDate := randomBirthDate() + salary := randomSalary() + phoneNumber := randomPhoneNumber() + isActive := rand.Intn(2) == 1 + isVerified := rand.Intn(2) == 1 + city := randomCity() + hobbies := randomHobbies() + + metadata := fmt.Sprintf( + `{"user_number": %d, "registration_source": "%s", "tags": [%d, %d, %d]}`, + userNum, + []string{"web", "mobile", "api"}[rand.Intn(3)], + rand.Intn(100), + rand.Intn(100), + rand.Intn(100), + ) + + insertSQL := ` + INSERT INTO users ( + first_name, last_name, email, age, birth_date, salary, + phone_number, is_active, is_verified, city, hobbies, metadata + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) + ON CONFLICT (email) DO NOTHING; + ` + + _, err := db.Exec( + ctx, + insertSQL, + firstName, + lastName, + email, + age, + birthDate, + salary, + phoneNumber, + isActive, + isVerified, + city, + hobbies, + metadata, + ) + + if err != nil { + return fmt.Errorf("error inserting user %d: %w", userNum, err) + } + + return nil +} + +func main() { + err := godotenv.Load() + if err != nil { + log.Println("Warning: could not load .env file") + } + + dbURL := os.Getenv("PG_FROM_DB_URL") + if dbURL == "" { + log.Fatal("PG_FROM_DB_URL environment variable not set") + } + + ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) + defer cancel() + + db, err := pgxpool.New(ctx, dbURL) + if err != nil { + log.Fatalf("Unable to create connection pool: %v", err) + } + defer db.Close() + + err = db.Ping(ctx) + if err != nil { + log.Fatalf("Unable to ping database: %v", err) + } + fmt.Println("✓ Connected to database successfully") + + fmt.Println("Creating users table...") + err = createUsersTable(db, ctx) + if err != nil { + log.Fatalf("Failed to create table: %v", err) + } + fmt.Println("✓ Users table created/already exists") + + fmt.Println("Clearing existing data...") + err = truncateUsersTable(db, ctx) + if err != nil { + log.Fatalf("Failed to truncate table: %v", err) + } + fmt.Println("✓ Table cleared") + + fmt.Println("Inserting 1000 user records...") + for i := 1; i <= 1000; i++ { + err := insertUserRecord(db, ctx, i) + if err != nil { + log.Printf("Warning: failed to insert user %d: %v", i, err) + } + + if i%100 == 0 { + fmt.Printf(" Progress: %d/1000 records inserted\n", i) + } + } + + fmt.Println("✓ All 1000 user records inserted successfully") + + var count int + err = db.QueryRow(ctx, "SELECT COUNT(*) FROM users;").Scan(&count) + if err != nil { + log.Printf("Warning: could not verify record count: %v", err) + } else { + fmt.Printf("✓ Final user count: %d\n", count) + } + + fmt.Println("\n✅ Fixture creation completed successfully!") +}