package main import ( "context" "fmt" "log" "time" "github.com/gofrs/uuid/v5" _ "github.com/jackc/pgx/v5/stdlib" "github.com/jmoiron/sqlx" "golang.org/x/crypto/bcrypt" "github.com/saveinmed/backend-go/internal/config" "github.com/saveinmed/backend-go/internal/domain" ) const ( seederSuperadminName = "SaveInMed Superadmin" seederSuperadminUsername = "superadmin" seederSuperadminEmail = "superadmin@saveinmed.com" seederSuperadminPassword = "sim-seeder-superadmin" ) func main() { cfg, err := config.Load() if err != nil { log.Fatalf("failed to load config: %v", err) } db, err := sqlx.Open("pgx", cfg.DatabaseURL) if err != nil { log.Fatalf("Failed to connect to DB: %v", err) } defer db.Close() if err := db.Ping(); err != nil { log.Fatalf("Failed to ping DB: %v", err) } ctx := context.Background() log.Println("🧹 Cleaning database...") cleanDB(ctx, db) log.Println("🌱 Seeding data...") seedData(ctx, db, *cfg) log.Println("✅ Seeding complete!") } func cleanDB(ctx context.Context, db *sqlx.DB) { tables := []string{ "reviews", "shipments", "payment_preferences", "orders", "order_items", "cart_items", "products", "companies", "users", "shipping_settings", } for _, table := range tables { _, err := db.ExecContext(ctx, fmt.Sprintf("TRUNCATE TABLE %s CASCADE", table)) if err != nil { // Ignore error if table doesn't exist or is empty log.Printf("Warning cleaning %s: %v", table, err) } } } func seedData(ctx context.Context, db *sqlx.DB, cfg config.Config) { // 1. Seed platform superadmin adminCompanyID := uuid.Must(uuid.NewV7()) createCompany(ctx, db, &domain.Company{ ID: adminCompanyID, CNPJ: "00000000000000", CorporateName: "SaveInMed Platform", Category: "platform", LicenseNumber: "SUPERADMIN", IsVerified: true, }) createUser(ctx, db, &domain.User{ CompanyID: adminCompanyID, Role: "superadmin", Name: seederSuperadminName, Username: seederSuperadminUsername, Email: seederSuperadminEmail, Superadmin: true, }, seederSuperadminPassword, cfg.PasswordPepper) // 2. Distributors (Sellers - SP Center) distributor1ID := uuid.Must(uuid.NewV7()) createCompany(ctx, db, &domain.Company{ ID: distributor1ID, CNPJ: "11111111000111", CorporateName: "Distribuidora Nacional", Category: "distribuidora", LicenseNumber: "DIST-001", IsVerified: true, Latitude: -23.55052, Longitude: -46.633308, }) createUser(ctx, db, &domain.User{ CompanyID: distributor1ID, Role: "Owner", Name: "Dono da Distribuidora", Username: "distribuidora", Email: "distribuidora@saveinmed.com", }, "123456", cfg.PasswordPepper) createShippingSettings(ctx, db, distributor1ID, -23.55052, -46.633308, "Rua da Distribuidora, 1000") // 2b. Second Distributor (Sellers - SP East Zone/Mooca) distributor2ID := uuid.Must(uuid.NewV7()) createCompany(ctx, db, &domain.Company{ ID: distributor2ID, CNPJ: "55555555000555", CorporateName: "Distribuidora Leste Ltda", Category: "distribuidora", LicenseNumber: "DIST-002", IsVerified: true, Latitude: -23.55952, Longitude: -46.593308, }) createUser(ctx, db, &domain.User{ CompanyID: distributor2ID, Role: "Owner", Name: "Gerente Leste", Username: "distribuidora_leste", Email: "leste@saveinmed.com", }, "123456", cfg.PasswordPepper) createShippingSettings(ctx, db, distributor2ID, -23.55952, -46.593308, "Rua da Mooca, 500") // 3. Pharmacies (Buyers) pharmacy1ID := uuid.Must(uuid.NewV7()) createCompany(ctx, db, &domain.Company{ ID: pharmacy1ID, CNPJ: "22222222000122", CorporateName: "Farmácia Central", Category: "farmacia", LicenseNumber: "FARM-001", IsVerified: true, Latitude: -23.56052, Longitude: -46.643308, }) createUser(ctx, db, &domain.User{ CompanyID: pharmacy1ID, Role: "Owner", Name: "Dono da Farmácia", Username: "farmacia", Email: "farmacia@saveinmed.com", }, "123456", cfg.PasswordPepper) pharmacy2ID := uuid.Must(uuid.NewV7()) createCompany(ctx, db, &domain.Company{ ID: pharmacy2ID, CNPJ: "33333333000133", CorporateName: "Drogarias Tiete", Category: "farmacia", LicenseNumber: "FARM-002", IsVerified: true, Latitude: -23.51052, Longitude: -46.613308, }) createUser(ctx, db, &domain.User{ CompanyID: pharmacy2ID, Role: "Owner", Name: "Gerente Tiete", Username: "farmacia2", Email: "tiete@saveinmed.com", }, "123456", cfg.PasswordPepper) // 4. Products // List of diverse products commonMeds := []struct { Name string Price int64 // Base price }{ {"Dipirona Sódica 500mg", 450}, {"Paracetamol 750mg", 600}, {"Ibuprofeno 400mg", 1100}, {"Amoxicilina 500mg", 2200}, {"Omeprazol 20mg", 1400}, {"Simeticona 40mg", 700}, {"Dorflex 36cp", 1850}, {"Neosaldina 30dg", 2100}, {"Torsilax 30cp", 1200}, {"Cimegripe 20caps", 1300}, {"Losartana Potássica 50mg", 800}, {"Atenolol 25mg", 950}, {"Metformina 850mg", 750}, {"Sildenafila 50mg", 1500}, {"Azitromicina 500mg", 2800}, } var productIDs []uuid.UUID // Seed products for Dist 1 for i, p := range commonMeds { id := uuid.Must(uuid.NewV7()) // expiry := time.Now().AddDate(1, 0, 0) // Vary price slightly finalPrice := p.Price + int64(i*10) - 50 if finalPrice < 100 { finalPrice = 100 } createProduct(ctx, db, &domain.Product{ ID: id, SellerID: distributor1ID, Name: p.Name, Description: "Medicamento genérico de alta qualidade (Nacional)", // Batch/ExpiresAt/Stock removed PriceCents: finalPrice, }) // Keep first 5 for orders if i < 5 { productIDs = append(productIDs, id) } } // Seed products for Dist 2 (Leste) - Only some of them, different prices for i, p := range commonMeds { if i%2 == 0 { continue } // Skip half id := uuid.Must(uuid.NewV7()) // expiry := time.Now().AddDate(0, 6, 0) // Removed // Cheaper but fewer stock finalPrice := p.Price - 100 if finalPrice < 100 { finalPrice = 100 } createProduct(ctx, db, &domain.Product{ ID: id, SellerID: distributor2ID, Name: p.Name, Description: "Distribuição exclusiva ZL", // Batch/ExpiresAt/Stock removed PriceCents: finalPrice, }) } // 5. Orders // Order 1: Pharmacy 1 buying from Dist 1 orderID := uuid.Must(uuid.NewV7()) totalCents := int64(0) // Items qty := int64(10) priceItem := productIDs[0] // Dipirona from Dist 1 // We need to fetch price ideally but we know logic... let's just reuse base logic approx or fetch from struct above // Simulating price: unitPrice := commonMeds[0].Price - 50 // Same logic as above: p.Price + 0*10 - 50 itemTotal := unitPrice * qty totalCents += itemTotal createOrder(ctx, db, &domain.Order{ ID: orderID, BuyerID: pharmacy1ID, SellerID: distributor1ID, Status: "Faturado", // Ready for "Confirmar Entrega" test TotalCents: totalCents, CreatedAt: time.Now().AddDate(0, 0, -2), UpdatedAt: time.Now(), }) createOrderItem(ctx, db, &domain.OrderItem{ ID: uuid.Must(uuid.NewV7()), OrderID: orderID, ProductID: priceItem, Quantity: qty, UnitCents: unitPrice, Batch: "BATCH-NAC-" + priceItem.String()[:4], ExpiresAt: time.Now().AddDate(1, 0, 0), }) // Order 2: Pharmacy 2 buying from Dist 1 (Pending) order2ID := uuid.Must(uuid.NewV7()) createOrder(ctx, db, &domain.Order{ ID: order2ID, BuyerID: pharmacy2ID, SellerID: distributor1ID, Status: "Pendente", TotalCents: 5000, CreatedAt: time.Now(), UpdatedAt: time.Now(), }) } func createCompany(ctx context.Context, db *sqlx.DB, c *domain.Company) { now := time.Now().UTC() c.CreatedAt = now c.UpdatedAt = now _, err := db.NamedExecContext(ctx, ` INSERT INTO companies (id, cnpj, corporate_name, category, license_number, is_verified, latitude, longitude, created_at, updated_at) VALUES (:id, :cnpj, :corporate_name, :category, :license_number, :is_verified, :latitude, :longitude, :created_at, :updated_at) `, c) if err != nil { log.Printf("Error creating company %s: %v", c.CorporateName, err) } } func createUser(ctx context.Context, db *sqlx.DB, u *domain.User, password, pepper string) { hashed, _ := bcrypt.GenerateFromPassword([]byte(password+pepper), bcrypt.DefaultCost) u.ID = uuid.Must(uuid.NewV7()) u.PasswordHash = string(hashed) u.CreatedAt = time.Now().UTC() u.UpdatedAt = time.Now().UTC() // Ensure email/username uniqueness is handled by DB constraint, usually we just insert _, err := db.NamedExecContext(ctx, ` INSERT INTO users (id, company_id, role, name, username, email, password_hash, email_verified, created_at, updated_at) VALUES (:id, :company_id, :role, :name, :username, :email, :password_hash, :email_verified, :created_at, :updated_at) `, u) if err != nil { log.Printf("Error creating user %s: %v", u.Username, err) } } func createProduct(ctx context.Context, db *sqlx.DB, p *domain.Product) { now := time.Now().UTC() p.CreatedAt = now p.UpdatedAt = now _, err := db.NamedExecContext(ctx, ` INSERT INTO products (id, seller_id, name, description, price_cents, created_at, updated_at) VALUES (:id, :seller_id, :name, :description, :price_cents, :created_at, :updated_at) `, p) if err != nil { log.Printf("Error creating product %s: %v", p.Name, err) } } func createShippingSettings(ctx context.Context, db *sqlx.DB, vendorID uuid.UUID, lat, lng float64, address string) { now := time.Now().UTC() settings := &domain.ShippingSettings{ VendorID: vendorID, Active: true, MaxRadiusKm: 50, PricePerKmCents: 150, // R$ 1.50 MinFeeCents: 1000, // R$ 10.00 FreeShippingThresholdCents: nil, PickupActive: true, PickupAddress: address, PickupHours: "Seg-Sex 08-18h", CreatedAt: now, UpdatedAt: now, Latitude: lat, Longitude: lng, } _, err := db.NamedExecContext(ctx, ` INSERT INTO shipping_settings ( vendor_id, active, max_radius_km, price_per_km_cents, min_fee_cents, free_shipping_threshold_cents, pickup_active, pickup_address, pickup_hours, latitude, longitude, created_at, updated_at ) VALUES ( :vendor_id, :active, :max_radius_km, :price_per_km_cents, :min_fee_cents, :free_shipping_threshold_cents, :pickup_active, :pickup_address, :pickup_hours, :latitude, :longitude, :created_at, :updated_at ) `, settings) if err != nil { log.Printf("Error creating shipping settings: %v", err) } } func createOrder(ctx context.Context, db *sqlx.DB, o *domain.Order) { _, err := db.NamedExecContext(ctx, ` INSERT INTO orders (id, buyer_id, seller_id, status, total_cents, created_at, updated_at) VALUES (:id, :buyer_id, :seller_id, :status, :total_cents, :created_at, :updated_at) `, o) if err != nil { log.Printf("Error creating order: %v", err) } } func createOrderItem(ctx context.Context, db *sqlx.DB, item *domain.OrderItem) { _, err := db.NamedExecContext(ctx, ` INSERT INTO order_items (id, order_id, product_id, quantity, unit_cents, batch, expires_at) VALUES (:id, :order_id, :product_id, :quantity, :unit_cents, :batch, :expires_at) `, item) if err != nil { log.Printf("Error creating order item: %v", err) } }