backend-ci.yml's `test -z "$(gofmt -l .)"` strict gate (added in
13c21ac11) failed on a backlog of unformatted files. None of the
85 files in this commit had been edited since the gate was added
because no push touched veza-backend-api/** in between, so the
gate never fired until today's CI fixes triggered it.
The diff is exclusively whitespace alignment in struct literals
and trailing-space comments. `go build ./...` and the full test
suite (with VEZA_SKIP_INTEGRATION=1 -short) pass identically.
225 lines
7.4 KiB
Go
225 lines
7.4 KiB
Go
package main
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// SeededProduct holds product data.
|
|
type SeededProduct struct {
|
|
ID string
|
|
SellerID string
|
|
Title string
|
|
Price float64
|
|
}
|
|
|
|
// SeedMarketplace creates products, orders, order_items, product_reviews,
|
|
// seller_stripe_accounts, and seller_balances.
|
|
func SeedMarketplace(db *sql.DB, cfg Config, users []SeededUser, tracks []SeededTrack) ([]SeededProduct, error) {
|
|
fmt.Println("\n═══ MARKETPLACE ═══")
|
|
|
|
artists := GetArtists(users)
|
|
listeners := GetListeners(users)
|
|
if len(artists) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
products := make([]SeededProduct, 0, cfg.Products)
|
|
|
|
// ── 1. Products ──────────────────────────────────────────────────────────
|
|
p := NewProgress("products", cfg.Products)
|
|
productRows := make([][]interface{}, 0, cfg.Products)
|
|
|
|
for i := 0; i < cfg.Products; i++ {
|
|
id := newUUID()
|
|
seller := artists[rng.Intn(len(artists))]
|
|
genres := GenreForArtist(rng.Intn(len(artists)))
|
|
genre := genres[0]
|
|
title := GenProductTitle(genre.Name)
|
|
desc := fmt.Sprintf("Professional %s content by %s", genre.Slug, seller.DisplayName)
|
|
price := float64(randInt(999, 29999)) / 100.0 // €9.99 - €299.99
|
|
ptype := pick(productTypes)
|
|
license := pick([]string{"non-exclusive", "exclusive", "non-exclusive"})
|
|
category := pick(productCategories)
|
|
bpm := randInt(genre.BPMRange[0], genre.BPMRange[1])
|
|
key := ""
|
|
if len(genre.Keys) > 0 {
|
|
key = pick(genre.Keys)
|
|
}
|
|
|
|
// Optionally link to a track
|
|
var trackID interface{}
|
|
if ptype == "beat" && len(tracks) > 0 && randChance(50) {
|
|
// Find a track by this seller
|
|
for _, t := range tracks {
|
|
if t.CreatorID == seller.ID {
|
|
trackID = t.ID
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
createdAt := RandomTimeAfter(seller.CreatedAt)
|
|
prod := SeededProduct{ID: id, SellerID: seller.ID, Title: title, Price: price}
|
|
products = append(products, prod)
|
|
|
|
productRows = append(productRows, []interface{}{
|
|
id, seller.ID, title, desc, price, "EUR", "active",
|
|
ptype, trackID, license, bpm, key, category,
|
|
createdAt, createdAt,
|
|
})
|
|
}
|
|
|
|
_, err := BulkInsert(db, "products",
|
|
"id, seller_id, title, description, price, currency, status, product_type, track_id, license_type, bpm, musical_key, category, created_at, updated_at",
|
|
productRows)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("insert products: %w", err)
|
|
}
|
|
p.Update(len(productRows))
|
|
p.Done()
|
|
|
|
// ── 2. Orders ────────────────────────────────────────────────────────────
|
|
p = NewProgress("orders + order_items", cfg.Orders)
|
|
orderRows := make([][]interface{}, 0, cfg.Orders)
|
|
itemRows := make([][]interface{}, 0, cfg.Orders*2)
|
|
|
|
allBuyers := append(listeners, users...) // Mix of listeners and all users
|
|
statuses := []string{"completed", "completed", "completed", "completed", "pending", "cancelled"}
|
|
|
|
for i := 0; i < cfg.Orders; i++ {
|
|
orderID := newUUID()
|
|
buyer := allBuyers[rng.Intn(len(allBuyers))]
|
|
status := pick(statuses)
|
|
createdAt := RandomTimeAfter(buyer.CreatedAt)
|
|
|
|
// 1-3 items per order
|
|
itemCount := randInt(1, 3)
|
|
totalAmount := 0.0
|
|
selectedProducts := pickN(products, itemCount)
|
|
|
|
for _, prod := range selectedProducts {
|
|
totalAmount += prod.Price
|
|
itemRows = append(itemRows, []interface{}{
|
|
newUUID(), orderID, prod.ID, prod.Price,
|
|
})
|
|
}
|
|
|
|
orderRows = append(orderRows, []interface{}{
|
|
orderID, buyer.ID, totalAmount, "EUR", status,
|
|
createdAt, createdAt,
|
|
})
|
|
}
|
|
|
|
_, err = BulkInsert(db, "orders",
|
|
"id, buyer_id, total_amount, currency, status, created_at, updated_at",
|
|
orderRows)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("insert orders: %w", err)
|
|
}
|
|
|
|
_, err = BulkInsert(db, "order_items",
|
|
"id, order_id, product_id, price",
|
|
itemRows)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("insert order_items: %w", err)
|
|
}
|
|
p.Update(cfg.Orders)
|
|
p.Done()
|
|
|
|
// ── 3. Product reviews ───────────────────────────────────────────────────
|
|
p = NewProgress("product_reviews", cfg.ProductReviews)
|
|
reviewRows := make([][]interface{}, 0, cfg.ProductReviews)
|
|
reviewSeen := make(map[string]bool)
|
|
|
|
reviewContents := []string{
|
|
"Excellent quality, exactly what I needed!", "Great sounds, well organized.",
|
|
"Good value for the price.", "Professional quality samples.",
|
|
"Perfect for my latest project.", "Would buy again!",
|
|
"Decent pack, some gems in there.", "Not what I expected but still useful.",
|
|
"Top-notch production quality.", "These beats are fire!",
|
|
"Clean mix, ready to use.", "Amazing variety in this pack.",
|
|
}
|
|
|
|
for i := 0; i < cfg.ProductReviews*2 && len(reviewRows) < cfg.ProductReviews; i++ {
|
|
prod := products[rng.Intn(len(products))]
|
|
reviewer := allBuyers[rng.Intn(len(allBuyers))]
|
|
if reviewer.ID == prod.SellerID {
|
|
continue
|
|
}
|
|
key := prod.ID + ":" + reviewer.ID
|
|
if reviewSeen[key] {
|
|
continue
|
|
}
|
|
reviewSeen[key] = true
|
|
reviewRows = append(reviewRows, []interface{}{
|
|
newUUID(), prod.ID, reviewer.ID,
|
|
randInt(3, 5), // rating 3-5 (realistic positive bias)
|
|
pick(reviewContents),
|
|
time.Now(), time.Now(),
|
|
})
|
|
}
|
|
|
|
_, _ = BulkInsert(db, "product_reviews",
|
|
"id, product_id, user_id, rating, content, created_at, updated_at",
|
|
reviewRows)
|
|
p.Update(len(reviewRows))
|
|
p.Done()
|
|
|
|
// ── 4. Seller stripe accounts ────────────────────────────────────────────
|
|
p = NewProgress("seller_stripe_accounts", len(artists))
|
|
sellerRows := make([][]interface{}, 0, len(artists))
|
|
|
|
for _, artist := range artists {
|
|
if !randChance(60) {
|
|
continue
|
|
}
|
|
sellerRows = append(sellerRows, []interface{}{
|
|
newUUID(), artist.ID,
|
|
fmt.Sprintf("acct_%s", newUUID()[:16]),
|
|
true, // is_onboarded
|
|
true, // payouts_enabled
|
|
true, // charges_enabled
|
|
"verified", // kyc_status
|
|
nil, nil, // kyc_verification_session_id, kyc_verified_at
|
|
nil, // kyc_last_error
|
|
time.Now(), time.Now(),
|
|
})
|
|
}
|
|
|
|
_, _ = BulkInsert(db, "seller_stripe_accounts",
|
|
"id, user_id, stripe_account_id, is_onboarded, payouts_enabled, charges_enabled, kyc_status, kyc_verification_session_id, kyc_verified_at, kyc_last_error, created_at, updated_at",
|
|
sellerRows)
|
|
p.Update(len(sellerRows))
|
|
p.Done()
|
|
|
|
// ── 5. Seller balances ───────────────────────────────────────────────────
|
|
p = NewProgress("seller_balances", len(artists))
|
|
balanceRows := make([][]interface{}, 0, len(artists))
|
|
for _, artist := range artists {
|
|
if !randChance(50) {
|
|
continue
|
|
}
|
|
totalEarned := int64(randInt(0, 100000))
|
|
totalPaid := totalEarned * int64(randInt(30, 80)) / 100
|
|
available := totalEarned - totalPaid - int64(randInt(0, 10000))
|
|
if available < 0 {
|
|
available = 0
|
|
}
|
|
pending := int64(randInt(0, 10000))
|
|
|
|
balanceRows = append(balanceRows, []interface{}{
|
|
newUUID(), artist.ID,
|
|
available, pending, totalEarned, totalPaid,
|
|
"EUR", time.Now(),
|
|
})
|
|
}
|
|
_, _ = BulkInsert(db, "seller_balances",
|
|
"id, seller_id, available_cents, pending_cents, total_earned_cents, total_paid_out_cents, currency, updated_at",
|
|
balanceRows)
|
|
p.Update(len(balanceRows))
|
|
p.Done()
|
|
|
|
return products, nil
|
|
}
|