This commit is contained in:
2026-08-16 12:47:06 +02:00
parent 1d3a9d5abd
commit 96f8c1115c
5 changed files with 187 additions and 15 deletions
+82 -5
View File
@@ -4,6 +4,8 @@ import (
"context"
"encoding/json"
"fmt"
"html"
"regexp"
"strings"
"github.com/descrybe/descrybe-v2/apps/api/internal/catalog"
@@ -12,6 +14,12 @@ import (
var v1PartialSteps = []string{"category", "title", "description", "attributes"}
const v1MetaDescriptionMaxChars = 155
var (
v1BreakTagRe = regexp.MustCompile(`(?i)<br\s*/?>`)
v1BlockEndRe = regexp.MustCompile(`(?i)</(p|div|li|h[1-6]|tr)>`)
)
// ParseV1ProcessingType mirrors legacy parseV1ProcessingTypeFromBody.
// Accepts string ("full" / step), JSON array of steps, or nil (defaults to full).
func ParseV1ProcessingType(raw any) (storageValue string, responseValue any, err error) {
@@ -197,6 +205,7 @@ func (p *Pipeline) LoadV1ProcessJobItems(ctx context.Context, companyID, jobID u
if p == nil || p.Pool == nil {
return nil, fmt.Errorf("pipeline not configured")
}
allowedAttrs := loadCompanyAttributeKeySet(ctx, p, companyID)
rows, err := p.Pool.Query(ctx, `
SELECT
COALESCE(r.gtin, p.product_id, '') AS ean,
@@ -284,15 +293,28 @@ func (p *Pipeline) LoadV1ProcessJobItems(ctx context.Context, companyID, jobID u
_ = json.Unmarshal(rawJSON, &rawData)
main, more := catalog.ExtractProductImages(mapped, rawData)
plainDesc := ""
if descTxt != nil {
plainDesc = v1PlainDescription(*descTxt)
}
var description any
if descTxt != nil && strings.TrimSpace(*descTxt) != "" {
description = []string{*descTxt}
if plainDesc != "" {
description = plainDesc
} else {
description = nil
}
eprelVal := extractEPRELFromAttrs(attrs)
attrs = SanitizeV1ProcessAttributes(attrs)
attrs = SanitizeV1ProcessAttributesAllowed(attrs, allowedAttrs)
metaTitleOut := nullIfEmptyPtr(metaTitle)
if metaTitleOut == nil {
metaTitleOut = nullIfEmptyPtr(title)
}
metaDescOut := nullIfEmptyPtr(metaDesc)
if metaDescOut == nil && plainDesc != "" {
metaDescOut = truncateRunes(plainDesc, v1MetaDescriptionMaxChars)
}
item := V1ProcessJobItem{
"ean": ean,
@@ -300,8 +322,8 @@ func (p *Pipeline) LoadV1ProcessJobItems(ctx context.Context, companyID, jobID u
"category": nullIfEmptyPtr(category),
"category_name": nullIfEmptyPtr(categoryName),
"title": nullIfEmptyPtr(title),
"meta_title": nullIfEmptyPtr(metaTitle),
"meta_description": nullIfEmptyPtr(metaDesc),
"meta_title": metaTitleOut,
"meta_description": metaDescOut,
"description": description,
"attributes": nil,
"main_image": nil,
@@ -336,6 +358,61 @@ func nullIfEmptyPtr(s *string) any {
return *s
}
// loadCompanyAttributeKeySet returns canonicalized attribute_key values for the company.
// On query failure returns an empty (non-nil) set so FilterAttributesByAllowed still
// restricts to coreCharacteristicAttrKeys only.
func loadCompanyAttributeKeySet(ctx context.Context, p *Pipeline, companyID uuid.UUID) map[string]struct{} {
out := map[string]struct{}{}
if p == nil || p.Pool == nil {
return out
}
rows, err := p.Pool.Query(ctx, `
SELECT attribute_key
FROM attributes
WHERE company_id = $1 AND COALESCE(attribute_key, '') <> ''`, companyID)
if err != nil {
return out
}
defer rows.Close()
for rows.Next() {
var key string
if err := rows.Scan(&key); err != nil {
continue
}
canon := canonicalizeAttrKey(key)
if canon == "" {
continue
}
out[canon] = struct{}{}
out[strings.ToLower(strings.TrimSpace(key))] = struct{}{}
}
return out
}
// v1PlainDescription normalizes feed HTML into a single plain-text string for the
// legacy process poll (description is a string, not a one-element array).
func v1PlainDescription(s string) string {
s = strings.TrimSpace(s)
if s == "" {
return ""
}
s = html.UnescapeString(s)
s = strings.ReplaceAll(s, "\u00a0", " ")
s = v1BreakTagRe.ReplaceAllString(s, "\n")
s = v1BlockEndRe.ReplaceAllString(s, "\n")
s = specsHTMLTagRe.ReplaceAllString(s, " ")
s = html.UnescapeString(s)
lines := strings.Split(s, "\n")
kept := make([]string, 0, len(lines))
for _, line := range lines {
line = strings.Join(strings.Fields(line), " ")
if line != "" {
kept = append(kept, line)
}
}
return strings.TrimSpace(strings.Join(kept, "\n"))
}
func extractEPRELFromAttrs(attrs map[string]any) any {
if attrs == nil {
return nil