237 lines
7.4 KiB
Go
237 lines
7.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
"github.com/joho/godotenv"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
func main() {
|
|
log.Println("Starting seeder...")
|
|
if err := godotenv.Load(); err != nil {
|
|
log.Println("No .env file found, relying on env vars")
|
|
}
|
|
|
|
dbURL := os.Getenv("DATABASE_URL")
|
|
if dbURL == "" {
|
|
log.Fatal("DATABASE_URL is not set")
|
|
}
|
|
|
|
pool, err := pgxpool.New(context.Background(), dbURL)
|
|
if err != nil {
|
|
log.Fatalf("Unable to connect to database: %v", err)
|
|
}
|
|
defer pool.Close()
|
|
|
|
ctx := context.Background()
|
|
|
|
// 1. Fix Schema (Missing Columns)
|
|
log.Println("Fixing schema...")
|
|
schemaFixes := []string{
|
|
`ALTER TABLE categories ADD COLUMN IF NOT EXISTS is_active BOOLEAN DEFAULT TRUE`,
|
|
`ALTER TABLE categories ADD COLUMN IF NOT EXISTS icon_url TEXT`,
|
|
`ALTER TABLE categories ADD COLUMN IF NOT EXISTS official_account_id UUID`,
|
|
`ALTER TABLE categories ADD COLUMN IF NOT EXISTS slug TEXT UNIQUE`,
|
|
`CREATE TABLE IF NOT EXISTS user_category_settings (
|
|
user_id UUID NOT NULL,
|
|
category_id UUID NOT NULL,
|
|
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
PRIMARY KEY (user_id, category_id)
|
|
)`,
|
|
}
|
|
|
|
for _, query := range schemaFixes {
|
|
_, err := pool.Exec(ctx, query)
|
|
if err != nil {
|
|
log.Printf("Warning executing schema fix: %s\nError: %v", query, err)
|
|
// Continue as column might exist or other non-fatal error
|
|
}
|
|
}
|
|
|
|
// 2. Seed Categories
|
|
log.Println("Seeding categories...")
|
|
categories := []struct {
|
|
Name string
|
|
Slug string
|
|
Description string
|
|
IconURL string
|
|
}{
|
|
{"Travel", "travel", "Explore the world", "✈️"},
|
|
{"Food", "food", "Delicious eats", "🍔"},
|
|
{"Tech", "tech", "Latest gadgets and software", "💻"},
|
|
{"Art", "art", "Creative expressions", "🎨"},
|
|
{"Music", "music", "Sounds and rhythms", "🎵"},
|
|
}
|
|
|
|
for _, cat := range categories {
|
|
var catID uuid.UUID
|
|
// Check if exists
|
|
err := pool.QueryRow(ctx, "SELECT id FROM categories WHERE slug = $1", cat.Slug).Scan(&catID)
|
|
if err == pgx.ErrNoRows {
|
|
// Create
|
|
err = pool.QueryRow(ctx, `
|
|
INSERT INTO categories (name, slug, description, icon_url, is_active)
|
|
VALUES ($1, $2, $3, $4, true)
|
|
RETURNING id
|
|
`, cat.Name, cat.Slug, cat.Description, cat.IconURL).Scan(&catID)
|
|
if err != nil {
|
|
log.Printf("Failed to create category %s: %v", cat.Name, err)
|
|
continue
|
|
}
|
|
log.Printf("Created category: %s", cat.Name)
|
|
} else if err != nil {
|
|
log.Printf("Error checking category %s: %v", cat.Name, err)
|
|
continue
|
|
} else {
|
|
// Update icon/active
|
|
_, err = pool.Exec(ctx, "UPDATE categories SET icon_url = $1, is_active = true WHERE id = $2", cat.IconURL, catID)
|
|
if err != nil {
|
|
log.Printf("Failed to update category %s: %v", cat.Name, err)
|
|
}
|
|
}
|
|
|
|
// 3. Create/Link Official Account
|
|
if err := ensureOfficialAccount(ctx, pool, cat.Name, catID); err != nil {
|
|
log.Printf("Failed to ensure official account for %s: %v", cat.Name, err)
|
|
}
|
|
}
|
|
|
|
// 4. Generate Random Users & Posts (Stress Test Data)
|
|
log.Println("Generating stress test data (50 users, 200 posts)...")
|
|
|
|
commonPasswordsBytes, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
|
|
commonPasswordHash := string(commonPasswordsBytes)
|
|
|
|
for i := 0; i < 50; i++ {
|
|
email := fmt.Sprintf("user_%d@stress.test", i)
|
|
handle := fmt.Sprintf("user_%d", i)
|
|
displayName := fmt.Sprintf("Stress User %d", i)
|
|
|
|
// Create User
|
|
var userID uuid.UUID
|
|
err := pool.QueryRow(ctx, "SELECT id FROM users WHERE email = $1", email).Scan(&userID)
|
|
if err == pgx.ErrNoRows {
|
|
err = pool.QueryRow(ctx, `
|
|
INSERT INTO users (email, encrypted_password, created_at, updated_at)
|
|
VALUES ($1, $2, NOW(), NOW())
|
|
RETURNING id
|
|
`, email, commonPasswordHash).Scan(&userID)
|
|
if err != nil {
|
|
log.Printf("Failed to create user %s: %v", email, err)
|
|
continue
|
|
}
|
|
|
|
// Create Profile
|
|
_, err = pool.Exec(ctx, `
|
|
INSERT INTO profiles (id, handle, display_name, created_at, updated_at)
|
|
VALUES ($1, $2, $3, NOW(), NOW())
|
|
`, userID, handle, displayName)
|
|
|
|
// Initialize Trust State
|
|
pool.Exec(ctx, "INSERT INTO trust_state (user_id) VALUES ($1)", userID)
|
|
|
|
// Initialize Privacy Settings
|
|
pool.Exec(ctx, "INSERT INTO profile_privacy_settings (user_id) VALUES ($1)", userID)
|
|
} else if err != nil {
|
|
continue
|
|
}
|
|
|
|
// Create 4-5 Posts per user
|
|
for j := 0; j < 5; j++ {
|
|
body := fmt.Sprintf("Stress test post #%d from user %d. #stress #test", j, i)
|
|
_, err := pool.Exec(ctx, `
|
|
INSERT INTO posts (author_id, body, status, visibility, created_at)
|
|
VALUES ($1, $2, 'active', 'public', NOW())
|
|
`, userID, body)
|
|
if err != nil {
|
|
log.Printf("Failed to create post for %s: %v", handle, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Println("Seeding complete.")
|
|
}
|
|
|
|
func ensureOfficialAccount(ctx context.Context, pool *pgxpool.Pool, catName string, catID uuid.UUID) error {
|
|
// Check if category has official account
|
|
var officialAccountID *uuid.UUID
|
|
err := pool.QueryRow(ctx, "SELECT official_account_id FROM categories WHERE id = $1", catID).Scan(&officialAccountID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if officialAccountID != nil {
|
|
// Exists, ensure profile is correct?
|
|
return nil // Assume good
|
|
}
|
|
|
|
// Create User
|
|
email := fmt.Sprintf("official_%s@sojorn.com", catName)
|
|
handle := fmt.Sprintf("sojorn_%s", catName)
|
|
displayName := fmt.Sprintf("Sojorn %s", catName)
|
|
password := "OfficialAccount123!" // Should change later
|
|
|
|
hashedBytes, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
|
passwordHash := string(hashedBytes)
|
|
|
|
var userID uuid.UUID
|
|
// Check user exists
|
|
err = pool.QueryRow(ctx, "SELECT id FROM users WHERE email = $1", email).Scan(&userID)
|
|
if err == pgx.ErrNoRows {
|
|
err = pool.QueryRow(ctx, `
|
|
INSERT INTO users (email, encrypted_password, created_at, updated_at)
|
|
VALUES ($1, $2, NOW(), NOW())
|
|
RETURNING id
|
|
`, email, passwordHash).Scan(&userID)
|
|
if err != nil {
|
|
return fmt.Errorf("creating user: %w", err)
|
|
}
|
|
} else if err != nil {
|
|
return fmt.Errorf("checking user: %w", err)
|
|
}
|
|
|
|
// Check/Create Profile
|
|
var profileID uuid.UUID
|
|
err = pool.QueryRow(ctx, "SELECT id FROM profiles WHERE id = $1", userID).Scan(&profileID)
|
|
if err == pgx.ErrNoRows {
|
|
_, err = pool.Exec(ctx, `
|
|
INSERT INTO profiles (id, handle, display_name, is_official, created_at, updated_at)
|
|
VALUES ($1, $2, $3, true, NOW(), NOW())
|
|
`, userID, handle, displayName)
|
|
if err != nil {
|
|
return fmt.Errorf("creating profile: %w", err)
|
|
}
|
|
} else if err != nil {
|
|
return fmt.Errorf("checking profile: %w", err)
|
|
}
|
|
|
|
// Update Category
|
|
_, err = pool.Exec(ctx, "UPDATE categories SET official_account_id = $1 WHERE id = $2", userID, catID)
|
|
if err != nil {
|
|
return fmt.Errorf("linking category: %w", err)
|
|
}
|
|
|
|
// Create Seed Posts
|
|
for i := 1; i <= 5; i++ {
|
|
body := fmt.Sprintf("Welcome to the official %s feed! Post #%d. #%s", catName, i, catName)
|
|
_, err := pool.Exec(ctx, `
|
|
INSERT INTO posts (author_id, category_id, body, status, visibility, created_at)
|
|
VALUES ($1, $2, $3, 'active', 'public', NOW())
|
|
`, userID, catID, body)
|
|
if err != nil {
|
|
log.Printf("Failed to seed post for %s: %v", catName, err)
|
|
}
|
|
}
|
|
|
|
log.Printf("Official account setup content for %s", catName)
|
|
return nil
|
|
}
|