saveinmed/backend/cmd/seeder/main.go
2026-03-04 10:41:40 -06:00

388 lines
11 KiB
Go

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)
}
}