Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,546 @@
|
||||
// Command seed-woo-demo inserts sample WooCommerce orders, line items, reviews,
|
||||
// and a draft campaign so UI + audience targeting work without a live store.
|
||||
//
|
||||
// When WOO_STORE_URL + WOO_CONSUMER_KEY + WOO_CONSUMER_SECRET are set (or -live),
|
||||
// also upserts woocommerce_configs and optionally tests the REST connection.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// go run ./cmd/seed-woo-demo -postgres "$DATABASE_URL"
|
||||
// go run ./cmd/seed-woo-demo -company "A1 Slovenija"
|
||||
// go run ./cmd/seed-woo-demo -live # require WOO_* and test connection
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/woocommerce"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
const (
|
||||
demoCategoryName = "Demo Electronics"
|
||||
demoCategoryUID = "demo-electronics"
|
||||
demoSKUPrefix = "DEMO-WOO-"
|
||||
)
|
||||
|
||||
type demoProduct struct {
|
||||
SKU string
|
||||
Name string
|
||||
Category string
|
||||
Price string
|
||||
WCID int64
|
||||
}
|
||||
|
||||
type demoCustomer struct {
|
||||
Email string
|
||||
Name string
|
||||
}
|
||||
|
||||
func main() {
|
||||
postgresURL := flag.String("postgres", os.Getenv("DATABASE_URL"), "Postgres URL")
|
||||
companyName := flag.String("company", "A1 Slovenija", "Target company name")
|
||||
live := flag.Bool("live", false, "Require WOO_* env and test REST connection after seeding")
|
||||
flag.Parse()
|
||||
|
||||
if strings.TrimSpace(*postgresURL) == "" {
|
||||
log.Fatal("-postgres / DATABASE_URL is required")
|
||||
}
|
||||
companyNameNorm := strings.TrimSpace(*companyName)
|
||||
if companyNameNorm == "" {
|
||||
log.Fatal("-company is required")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
pg, err := pgxpool.New(ctx, *postgresURL)
|
||||
if err != nil {
|
||||
log.Fatalf("postgres: %v", err)
|
||||
}
|
||||
defer pg.Close()
|
||||
|
||||
companyID, err := resolveCompany(ctx, pg, companyNameNorm)
|
||||
if err != nil {
|
||||
log.Fatalf("company: %v", err)
|
||||
}
|
||||
|
||||
tx, err := pg.Begin(ctx)
|
||||
if err != nil {
|
||||
log.Fatalf("begin: %v", err)
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
categoryID, err := ensureDemoCategory(ctx, tx, companyID)
|
||||
if err != nil {
|
||||
log.Fatalf("category: %v", err)
|
||||
}
|
||||
|
||||
products := []demoProduct{
|
||||
{SKU: demoSKUPrefix + "TV-50", Name: "Demo 4K TV 50\"", Category: demoCategoryUID, Price: "499.00", WCID: 90001},
|
||||
{SKU: demoSKUPrefix + "SOUND", Name: "Demo Soundbar", Category: demoCategoryUID, Price: "149.00", WCID: 90002},
|
||||
{SKU: demoSKUPrefix + "HEAD", Name: "Demo Wireless Headphones", Category: demoCategoryUID, Price: "89.00", WCID: 90003},
|
||||
}
|
||||
if err := seedDemoProducts(ctx, tx, companyID, products); err != nil {
|
||||
log.Fatalf("products: %v", err)
|
||||
}
|
||||
|
||||
customers := []demoCustomer{
|
||||
{Email: "anna.buyer@example.com", Name: "Anna Buyer"},
|
||||
{Email: "ben.buyer@example.com", Name: "Ben Buyer"},
|
||||
{Email: "cara.buyer@example.com", Name: "Cara Buyer"},
|
||||
{Email: "dan.other@example.com", Name: "Dan Other"},
|
||||
}
|
||||
orderCount, itemCount, err := seedDemoOrders(ctx, tx, companyID, products, customers)
|
||||
if err != nil {
|
||||
log.Fatalf("orders: %v", err)
|
||||
}
|
||||
reviewCount, err := seedDemoReviews(ctx, tx, companyID, products, customers)
|
||||
if err != nil {
|
||||
log.Fatalf("reviews: %v", err)
|
||||
}
|
||||
|
||||
encKey := woocommerce.DeriveKey(
|
||||
os.Getenv("CREDENTIALS_ENCRYPTION_KEY"),
|
||||
os.Getenv("TOKEN_SIGNING_SECRET")+"|"+*postgresURL,
|
||||
)
|
||||
storeURL, key, secret, fromEnv := wooCredsFromEnv()
|
||||
if err := upsertWooConfig(ctx, tx, companyID, encKey, storeURL, key, secret, fromEnv); err != nil {
|
||||
log.Fatalf("woocommerce_configs: %v", err)
|
||||
}
|
||||
|
||||
campaignID, err := seedDemoCampaign(ctx, tx, companyID, categoryID)
|
||||
if err != nil {
|
||||
log.Fatalf("campaign: %v", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(ctx); err != nil {
|
||||
log.Fatalf("commit: %v", err)
|
||||
}
|
||||
|
||||
woo := &woocommerce.Service{Pool: pg}
|
||||
audience, err := woo.AudienceBoughtCategories(ctx, companyID, demoCategoryName, "", 100)
|
||||
if err != nil {
|
||||
log.Fatalf("audience check: %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("=== Descrybe v2 WooCommerce demo seed ===")
|
||||
fmt.Printf("company: %s (%s)\n", companyNameNorm, companyID)
|
||||
fmt.Printf("category: %s (%s)\n", demoCategoryName, categoryID)
|
||||
fmt.Printf("demo products: %d (SKU prefix %s)\n", len(products), demoSKUPrefix)
|
||||
fmt.Printf("orders upserted: %d\n", orderCount)
|
||||
fmt.Printf("order items: %d\n", itemCount)
|
||||
fmt.Printf("reviews upserted: %d\n", reviewCount)
|
||||
fmt.Printf("audience (%s): %d customers\n", demoCategoryName, audience.Total)
|
||||
for _, c := range audience.Customers {
|
||||
fmt.Printf(" - %s <%s>\n", c.Name, c.Email)
|
||||
}
|
||||
fmt.Printf("draft campaign: %s\n", campaignID)
|
||||
fmt.Printf("config store_url: %s\n", storeURL)
|
||||
if fromEnv {
|
||||
fmt.Println("credentials: from WOO_* env (encrypted at rest)")
|
||||
} else {
|
||||
fmt.Println("credentials: demo placeholders (no live REST)")
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Println("Next:")
|
||||
fmt.Println(" 1. Open /woocommerce — Orders & Reviews tabs should list seeded rows")
|
||||
fmt.Println(" 2. POST /api/woocommerce/audience {\"bought_category\":\"Demo Electronics\"}")
|
||||
fmt.Println(" 3. Open /campaigns — draft \"Woo demo — purchased electronics\" uses purchased audience")
|
||||
fmt.Println(" 4. Live store: set WOO_STORE_URL / WOO_CONSUMER_KEY / WOO_CONSUMER_SECRET then re-run with -live")
|
||||
|
||||
if *live || (fromEnv && flag.Lookup("live").Value.String() == "true") {
|
||||
// handled below when -live
|
||||
}
|
||||
if *live {
|
||||
if !fromEnv {
|
||||
log.Fatal("-live requires WOO_STORE_URL, WOO_CONSUMER_KEY, WOO_CONSUMER_SECRET")
|
||||
}
|
||||
client := woocommerce.NewClient(storeURL, key, secret, &http.Client{Timeout: 20 * time.Second})
|
||||
if err := client.TestConnection(ctx); err != nil {
|
||||
log.Fatalf("live Woo test failed: %v", err)
|
||||
}
|
||||
fmt.Println("live Woo test: OK")
|
||||
} else if fromEnv {
|
||||
client := woocommerce.NewClient(storeURL, key, secret, &http.Client{Timeout: 20 * time.Second})
|
||||
if err := client.TestConnection(ctx); err != nil {
|
||||
fmt.Printf("live Woo test: skipped/failed (%v) — demo DB seed still applied\n", err)
|
||||
} else {
|
||||
fmt.Println("live Woo test: OK (WOO_* present)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resolveCompany(ctx context.Context, pg *pgxpool.Pool, name string) (uuid.UUID, error) {
|
||||
var id uuid.UUID
|
||||
err := pg.QueryRow(ctx, `
|
||||
SELECT id FROM companies WHERE name = $1 ORDER BY updated_at DESC LIMIT 1`, name).Scan(&id)
|
||||
if err != nil {
|
||||
return uuid.Nil, fmt.Errorf("%q: %w", name, err)
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func ensureDemoCategory(ctx context.Context, tx pgx.Tx, companyID uuid.UUID) (uuid.UUID, error) {
|
||||
var id uuid.UUID
|
||||
err := tx.QueryRow(ctx, `
|
||||
INSERT INTO categories (company_id, name, unique_id, path, level, position, is_active, updated_at)
|
||||
VALUES ($1, $2, $3, $2, 0, 0, true, now())
|
||||
ON CONFLICT (company_id, unique_id) DO UPDATE SET
|
||||
name = EXCLUDED.name,
|
||||
is_active = true,
|
||||
updated_at = now()
|
||||
RETURNING id`, companyID, demoCategoryName, demoCategoryUID).Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
func seedDemoProducts(ctx context.Context, tx pgx.Tx, companyID uuid.UUID, products []demoProduct) error {
|
||||
for _, p := range products {
|
||||
mapped, _ := json.Marshal(map[string]any{
|
||||
"sku": p.SKU,
|
||||
"price": p.Price,
|
||||
"regular_price": p.Price,
|
||||
"images": []string{},
|
||||
"source": "seed-woo-demo",
|
||||
})
|
||||
attrs, _ := json.Marshal(map[string]any{"Brand": "Descrybe Demo"})
|
||||
_, err := tx.Exec(ctx, `
|
||||
INSERT INTO processed_products (
|
||||
company_id, product_id, name, category, description, processed_name, processed_description,
|
||||
attributes, processed_attributes, status, updated_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $3, $5, $6::jsonb, $6::jsonb, 'completed', now()
|
||||
)
|
||||
ON CONFLICT DO NOTHING`,
|
||||
companyID, p.SKU, p.Name, p.Category,
|
||||
"Seeded demo product for WooCommerce integration testing.", attrs)
|
||||
if err != nil {
|
||||
// processed_products may lack a unique on product_id; fall back to upsert-by-lookup.
|
||||
var existing uuid.UUID
|
||||
qerr := tx.QueryRow(ctx, `
|
||||
SELECT id FROM processed_products
|
||||
WHERE company_id = $1 AND product_id = $2 LIMIT 1`, companyID, p.SKU).Scan(&existing)
|
||||
if qerr == nil {
|
||||
_, err = tx.Exec(ctx, `
|
||||
UPDATE processed_products SET
|
||||
name = $3, category = $4, description = $5, processed_name = $3,
|
||||
processed_description = $5, attributes = $6::jsonb, processed_attributes = $6::jsonb,
|
||||
status = 'completed', updated_at = now()
|
||||
WHERE id = $2 AND company_id = $1`,
|
||||
companyID, existing, p.Name, p.Category,
|
||||
"Seeded demo product for WooCommerce integration testing.", attrs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if qerr == pgx.ErrNoRows {
|
||||
_, err = tx.Exec(ctx, `
|
||||
INSERT INTO processed_products (
|
||||
company_id, product_id, name, category, description, processed_name, processed_description,
|
||||
attributes, processed_attributes, status, updated_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $3, $5, $6::jsonb, $6::jsonb, 'completed', now()
|
||||
)`,
|
||||
companyID, p.SKU, p.Name, p.Category,
|
||||
"Seeded demo product for WooCommerce integration testing.", attrs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return qerr
|
||||
}
|
||||
}
|
||||
_ = mapped
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func seedDemoOrders(ctx context.Context, tx pgx.Tx, companyID uuid.UUID, products []demoProduct, customers []demoCustomer) (int, int, error) {
|
||||
type line struct {
|
||||
product demoProduct
|
||||
qty int
|
||||
cats []string
|
||||
}
|
||||
type orderSpec struct {
|
||||
externalID int64
|
||||
status string
|
||||
customer demoCustomer
|
||||
total string
|
||||
lines []line
|
||||
daysAgo int
|
||||
}
|
||||
|
||||
specs := []orderSpec{
|
||||
{
|
||||
externalID: 88001, status: "completed", customer: customers[0], total: "648.00", daysAgo: 12,
|
||||
lines: []line{
|
||||
{product: products[0], qty: 1, cats: []string{demoCategoryName}},
|
||||
{product: products[1], qty: 1, cats: []string{demoCategoryName}},
|
||||
},
|
||||
},
|
||||
{
|
||||
externalID: 88002, status: "processing", customer: customers[1], total: "89.00", daysAgo: 5,
|
||||
lines: []line{{product: products[2], qty: 1, cats: []string{demoCategoryName}}},
|
||||
},
|
||||
{
|
||||
externalID: 88003, status: "completed", customer: customers[2], total: "499.00", daysAgo: 3,
|
||||
lines: []line{{product: products[0], qty: 1, cats: []string{demoCategoryName}}},
|
||||
},
|
||||
{
|
||||
externalID: 88004, status: "completed", customer: customers[3], total: "29.00", daysAgo: 8,
|
||||
lines: []line{{
|
||||
product: demoProduct{SKU: "OTHER-SKU-1", Name: "Demo Cable Pack", Price: "29.00", WCID: 90100},
|
||||
qty: 1,
|
||||
cats: []string{"Accessories"},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
orders := 0
|
||||
items := 0
|
||||
for _, spec := range specs {
|
||||
payload, _ := json.Marshal(map[string]any{
|
||||
"id": spec.externalID,
|
||||
"status": spec.status,
|
||||
"currency": "EUR",
|
||||
"total": spec.total,
|
||||
"billing": map[string]any{"email": spec.customer.Email, "first_name": strings.Split(spec.customer.Name, " ")[0]},
|
||||
"line_items": len(spec.lines),
|
||||
"seed": "seed-woo-demo",
|
||||
})
|
||||
orderedAt := time.Now().UTC().Add(-time.Duration(spec.daysAgo) * 24 * time.Hour)
|
||||
var orderID uuid.UUID
|
||||
err := tx.QueryRow(ctx, `
|
||||
INSERT INTO woo_orders (
|
||||
company_id, external_id, status, currency, total, customer_email, customer_name,
|
||||
ordered_at, payload, synced_at, updated_at
|
||||
) VALUES (
|
||||
$1, $2, $3, 'EUR', $4::numeric, $5, $6, $7, $8::jsonb, now(), now()
|
||||
)
|
||||
ON CONFLICT (company_id, external_id) DO UPDATE SET
|
||||
status = EXCLUDED.status,
|
||||
total = EXCLUDED.total,
|
||||
customer_email = EXCLUDED.customer_email,
|
||||
customer_name = EXCLUDED.customer_name,
|
||||
ordered_at = EXCLUDED.ordered_at,
|
||||
payload = EXCLUDED.payload,
|
||||
synced_at = now(),
|
||||
updated_at = now()
|
||||
RETURNING id`,
|
||||
companyID, spec.externalID, spec.status, spec.total,
|
||||
strings.ToLower(spec.customer.Email), spec.customer.Name, orderedAt, payload,
|
||||
).Scan(&orderID)
|
||||
if err != nil {
|
||||
return orders, items, err
|
||||
}
|
||||
orders++
|
||||
|
||||
if _, err := tx.Exec(ctx, `DELETE FROM woo_order_items WHERE company_id = $1 AND order_id = $2`, companyID, orderID); err != nil {
|
||||
return orders, items, err
|
||||
}
|
||||
for i, ln := range spec.lines {
|
||||
catsRaw, _ := json.Marshal(ln.cats)
|
||||
itemPayload, _ := json.Marshal(map[string]any{
|
||||
"id": int64(spec.externalID*10 + int64(i+1)),
|
||||
"product_id": ln.product.WCID,
|
||||
"sku": ln.product.SKU,
|
||||
"name": ln.product.Name,
|
||||
"quantity": ln.qty,
|
||||
"total": ln.product.Price,
|
||||
"categories": ln.cats,
|
||||
})
|
||||
_, err := tx.Exec(ctx, `
|
||||
INSERT INTO woo_order_items (
|
||||
company_id, order_id, external_id, product_id, sku, name, quantity, total, categories, payload, updated_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8::numeric, $9::jsonb, $10::jsonb, now()
|
||||
)`,
|
||||
companyID, orderID, spec.externalID*10+int64(i+1), ln.product.WCID,
|
||||
ln.product.SKU, ln.product.Name, ln.qty, ln.product.Price, catsRaw, itemPayload,
|
||||
)
|
||||
if err != nil {
|
||||
return orders, items, err
|
||||
}
|
||||
items++
|
||||
}
|
||||
}
|
||||
return orders, items, nil
|
||||
}
|
||||
|
||||
func seedDemoReviews(ctx context.Context, tx pgx.Tx, companyID uuid.UUID, products []demoProduct, customers []demoCustomer) (int, error) {
|
||||
type rev struct {
|
||||
externalID int64
|
||||
product demoProduct
|
||||
customer demoCustomer
|
||||
rating int
|
||||
status string
|
||||
body string
|
||||
daysAgo int
|
||||
}
|
||||
specs := []rev{
|
||||
{88011, products[0], customers[0], 5, "approved", "Picture quality is excellent for the price.", 10},
|
||||
{88012, products[2], customers[1], 4, "approved", "Comfortable and clear sound.", 4},
|
||||
{88013, products[1], customers[2], 3, "hold", "Good bass, wish the remote was better.", 2},
|
||||
}
|
||||
n := 0
|
||||
for _, r := range specs {
|
||||
payload, _ := json.Marshal(map[string]any{
|
||||
"id": r.externalID, "product_id": r.product.WCID, "rating": r.rating, "seed": "seed-woo-demo",
|
||||
})
|
||||
reviewedAt := time.Now().UTC().Add(-time.Duration(r.daysAgo) * 24 * time.Hour)
|
||||
_, err := tx.Exec(ctx, `
|
||||
INSERT INTO product_reviews (
|
||||
company_id, external_id, product_id, product_name, status, reviewer, reviewer_email,
|
||||
rating, review, reviewed_at, payload, synced_at, updated_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11::jsonb, now(), now()
|
||||
)
|
||||
ON CONFLICT (company_id, external_id) DO UPDATE SET
|
||||
product_id = EXCLUDED.product_id,
|
||||
product_name = EXCLUDED.product_name,
|
||||
status = EXCLUDED.status,
|
||||
reviewer = EXCLUDED.reviewer,
|
||||
reviewer_email = EXCLUDED.reviewer_email,
|
||||
rating = EXCLUDED.rating,
|
||||
review = EXCLUDED.review,
|
||||
reviewed_at = EXCLUDED.reviewed_at,
|
||||
payload = EXCLUDED.payload,
|
||||
synced_at = now(),
|
||||
updated_at = now()`,
|
||||
companyID, r.externalID, r.product.WCID, r.product.Name, r.status,
|
||||
r.customer.Name, strings.ToLower(r.customer.Email), r.rating, r.body, reviewedAt, payload,
|
||||
)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
n++
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func wooCredsFromEnv() (storeURL, key, secret string, ok bool) {
|
||||
storeURL = strings.TrimSpace(os.Getenv("WOO_STORE_URL"))
|
||||
if storeURL == "" {
|
||||
storeURL = strings.TrimSpace(os.Getenv("WOOCOMMERCE_STORE_URL"))
|
||||
}
|
||||
key = strings.TrimSpace(os.Getenv("WOO_CONSUMER_KEY"))
|
||||
if key == "" {
|
||||
key = strings.TrimSpace(os.Getenv("WOOCOMMERCE_CONSUMER_KEY"))
|
||||
}
|
||||
secret = strings.TrimSpace(os.Getenv("WOO_CONSUMER_SECRET"))
|
||||
if secret == "" {
|
||||
secret = strings.TrimSpace(os.Getenv("WOOCOMMERCE_CONSUMER_SECRET"))
|
||||
}
|
||||
if storeURL != "" && key != "" && secret != "" {
|
||||
return storeURL, key, secret, true
|
||||
}
|
||||
return "https://demo.woocommerce.local", "ck_demo_placeholder", "cs_demo_placeholder", false
|
||||
}
|
||||
|
||||
func upsertWooConfig(ctx context.Context, tx pgx.Tx, companyID uuid.UUID, encKey []byte, storeURL, key, secret string, live bool) error {
|
||||
normalized, err := woocommerce.NormalizeStoreURL(storeURL)
|
||||
if err != nil {
|
||||
if errors.Is(err, woocommerce.ErrBlockedStoreURL) || live {
|
||||
return err
|
||||
}
|
||||
// Offline demo placeholder only (never a private/metadata IP).
|
||||
normalized = "https://demo.woocommerce.local"
|
||||
}
|
||||
keyEnc, err := woocommerce.EncryptSecret(encKey, key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
secretEnc, err := woocommerce.EncryptSecret(encKey, secret)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
opt := woocommerce.SyncOptions{
|
||||
MatchStrategy: "sku",
|
||||
LastSyncStatus: "success",
|
||||
LastOrdersSyncStatus: "success",
|
||||
LastReviewsSyncStatus: "success",
|
||||
LastOrdersSyncedAt: &now,
|
||||
LastReviewsSyncedAt: &now,
|
||||
ProductIDs: map[string]int{},
|
||||
CategoryMappings: map[string]woocommerce.CategoryMap{},
|
||||
AttributeMappings: map[string]woocommerce.AttributeMap{},
|
||||
}
|
||||
raw, err := json.Marshal(opt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
enabled := live
|
||||
testStatus := "demo"
|
||||
if live {
|
||||
testStatus = "ok"
|
||||
}
|
||||
_, err = tx.Exec(ctx, `
|
||||
INSERT INTO woocommerce_configs (
|
||||
company_id, store_url, consumer_key, consumer_secret, is_enabled, sync_options,
|
||||
last_synced_at, last_test_at, last_test_status, updated_at
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6::jsonb, now(), now(), $7, now()
|
||||
)
|
||||
ON CONFLICT (company_id) DO UPDATE SET
|
||||
store_url = EXCLUDED.store_url,
|
||||
consumer_key = EXCLUDED.consumer_key,
|
||||
consumer_secret = EXCLUDED.consumer_secret,
|
||||
is_enabled = EXCLUDED.is_enabled,
|
||||
sync_options = EXCLUDED.sync_options,
|
||||
last_synced_at = EXCLUDED.last_synced_at,
|
||||
last_test_at = EXCLUDED.last_test_at,
|
||||
last_test_status = EXCLUDED.last_test_status,
|
||||
updated_at = now()`,
|
||||
companyID, normalized, keyEnc, secretEnc, enabled, raw, testStatus)
|
||||
return err
|
||||
}
|
||||
|
||||
func seedDemoCampaign(ctx context.Context, tx pgx.Tx, companyID, categoryID uuid.UUID) (uuid.UUID, error) {
|
||||
af, _ := json.Marshal(map[string]any{
|
||||
"type": "purchased",
|
||||
"category_ids": []string{categoryID.String()},
|
||||
"bought_category": demoCategoryName,
|
||||
"bought_categories": []string{demoCategoryName},
|
||||
})
|
||||
name := "Woo demo — purchased electronics"
|
||||
var id uuid.UUID
|
||||
err := tx.QueryRow(ctx, `
|
||||
SELECT id FROM email_campaigns
|
||||
WHERE company_id = $1 AND name = $2
|
||||
ORDER BY created_at DESC LIMIT 1`, companyID, name).Scan(&id)
|
||||
if err == nil {
|
||||
_, err = tx.Exec(ctx, `
|
||||
UPDATE email_campaigns SET
|
||||
category_ids = ARRAY[$2]::uuid[],
|
||||
audience_filter = $3::jsonb,
|
||||
status = 'draft',
|
||||
updated_at = now()
|
||||
WHERE id = $4 AND company_id = $1`,
|
||||
companyID, categoryID, af, id)
|
||||
return id, err
|
||||
}
|
||||
if err != pgx.ErrNoRows {
|
||||
return uuid.Nil, err
|
||||
}
|
||||
err = tx.QueryRow(ctx, `
|
||||
INSERT INTO email_campaigns (
|
||||
company_id, name, template_key, status, category_ids, product_ids,
|
||||
prompt, use_default_prompt, audience_filter, updated_at
|
||||
) VALUES (
|
||||
$1, $2, 'black_friday', 'draft', ARRAY[$3]::uuid[], ARRAY[]::uuid[],
|
||||
'Highlight Demo Electronics for past buyers.', true, $4::jsonb, now()
|
||||
)
|
||||
RETURNING id`, companyID, name, categoryID, af).Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
Reference in New Issue
Block a user