This commit is contained in:
2026-08-16 16:57:36 +02:00
parent 96f8c1115c
commit 532d439c41
106 changed files with 10147 additions and 494 deletions
@@ -0,0 +1,110 @@
package processing
import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
)
// BackfillCompanyProcessedAttributes rewrites attributes and processed_attributes
// for every processed row in a company using AttrsForPersist + category_attributes
// allowlists (same rules as processOne / enhance). Returns rows updated.
func BackfillCompanyProcessedAttributes(ctx context.Context, pool *pgxpool.Pool, companyID uuid.UUID) (int64, error) {
if pool == nil {
return 0, fmt.Errorf("backfill attributes: nil pool")
}
p := &Pipeline{Pool: pool}
sets := p.loadCategoryAttributeKeySets(ctx, companyID, uuid.Nil)
rows, err := pool.Query(ctx, `
SELECT id, COALESCE(category, ''), COALESCE(attributes, '{}'::jsonb), COALESCE(processed_attributes, '{}'::jsonb)
FROM processed_products
WHERE company_id = $1`, companyID)
if err != nil {
return 0, fmt.Errorf("backfill attributes query: %w", err)
}
defer rows.Close()
var updated int64
for rows.Next() {
var (
id uuid.UUID
category string
attrsJSON []byte
procAttrsJSON []byte
)
if err := rows.Scan(&id, &category, &attrsJSON, &procAttrsJSON); err != nil {
return updated, fmt.Errorf("backfill attributes scan: %w", err)
}
attrs := decodeAttrMap(attrsJSON)
procAttrs := decodeAttrMap(procAttrsJSON)
allowed := allowedAttrKeysFromSets(sets, category)
cleanAttrs := AttrsForPersist(attrs, allowed)
cleanProc := AttrsForPersist(procAttrs, allowed)
if len(cleanProc) == 0 {
cleanProc = cleanAttrs
}
newAttrsJSON, err := json.Marshal(cleanAttrs)
if err != nil {
return updated, fmt.Errorf("backfill attributes marshal attrs %s: %w", id, err)
}
newProcJSON, err := json.Marshal(cleanProc)
if err != nil {
return updated, fmt.Errorf("backfill attributes marshal processed %s: %w", id, err)
}
if bytes.Equal(compactJSON(attrsJSON), compactJSON(newAttrsJSON)) &&
bytes.Equal(compactJSON(procAttrsJSON), compactJSON(newProcJSON)) {
continue
}
ct, err := pool.Exec(ctx, `
UPDATE processed_products
SET attributes = $2::jsonb,
processed_attributes = $3::jsonb,
updated_at = now()
WHERE id = $1 AND company_id = $4`, id, string(newAttrsJSON), string(newProcJSON), companyID)
if err != nil {
return updated, fmt.Errorf("backfill attributes update %s: %w", id, err)
}
updated += ct.RowsAffected()
}
if err := rows.Err(); err != nil {
return updated, fmt.Errorf("backfill attributes rows: %w", err)
}
return updated, nil
}
func decodeAttrMap(raw []byte) map[string]any {
out := map[string]any{}
if len(raw) == 0 {
return out
}
var parsed any
if err := json.Unmarshal(raw, &parsed); err != nil {
return out
}
switch t := parsed.(type) {
case map[string]any:
return t
case []any:
for _, entry := range t {
if m, ok := entry.(map[string]any); ok {
if k, ok := m["key"].(string); ok && k != "" {
out[k] = m
}
}
}
}
return out
}
func compactJSON(b []byte) []byte {
var buf bytes.Buffer
if err := json.Compact(&buf, b); err != nil {
return b
}
return buf.Bytes()
}