This commit is contained in:
2026-08-17 21:20:45 +02:00
parent 321f11e817
commit 6fcdc74843
157 changed files with 2895 additions and 7544 deletions
+85 -16
View File
@@ -13,6 +13,7 @@ import (
"github.com/descrybe/descrybe-v2/apps/api/internal/company"
"github.com/descrybe/descrybe-v2/apps/api/internal/eprel"
"github.com/google/uuid"
"github.com/jackc/pgx/v5/pgxpool"
)
var v1PartialSteps = []string{"category", "title", "description", "attributes"}
@@ -274,7 +275,7 @@ func (p *Pipeline) LoadV1ProcessJobItems(ctx context.Context, companyID, jobID u
"error": "Product data not available",
}
if itemError != nil && *itemError != "" {
item["error"] = *itemError
item["error"] = PublicV1Error(*itemError)
}
full = append(full, item)
continue
@@ -309,6 +310,9 @@ func (p *Pipeline) LoadV1ProcessJobItems(ctx context.Context, companyID, jobID u
if descTxt != nil {
descOut = v1PreserveDescription(*descTxt)
}
if isPromptLeakageTitle(descOut) || isPromptLabelTitle(descOut) {
descOut = ""
}
eprelVal := extractEPRELFromAttrs(attrs)
attrs = SanitizeV1ProcessAttributesAllowed(attrs, allowedAttrs)
@@ -453,7 +457,7 @@ func (p *Pipeline) LoadV1ProcessJobItems(ctx context.Context, companyID, jobID u
// Internal ids (id / processed_product_id / raw_product_id) are omitted from
// the public V1 process item shape — use catalog APIs when a UUID is needed.
if itemError != nil && *itemError != "" {
item["error"] = *itemError
item["error"] = PublicV1Error(*itemError)
}
if len(attrs) > 0 {
item["attributes"] = attrs
@@ -484,35 +488,72 @@ func nullIfEmptyPtr(s *string) any {
return *s
}
// companyOmitsSEOMeta is true when V1/process must omit meta_title / meta_description:
// A1 cohort (legacy id), Platform Demo (A1-cloned prompts, no legacy id), or any
// company whose categories store A1-style role-section enhance prompts.
func companyOmitsSEOMeta(ctx context.Context, p *Pipeline, companyID uuid.UUID) bool {
if p == nil || p.Pool == nil {
// Known v2 companies.id values that omit SEO meta on V1 process payloads.
// A1 Slovenija is also matched via billing.A1LegacyCompanyID (legacy_company_id / PK).
const (
a1V2CompanyID = "604f23a8-b66e-4b21-8b45-0d72b68f4790"
platformDemoV2CompanyID = "2b3159b0-fc08-415b-b248-35ed02a6baab"
)
// CompanyOmitsSEOMetaID is true for A1 Slovenija and Platform Demo by companies.id
// (or the migrated MySQL company id). Does not grant Legacy plan privileges.
func CompanyOmitsSEOMetaID(companyID uuid.UUID) bool {
if companyID == uuid.Nil {
return false
}
id := strings.ToLower(companyID.String())
switch id {
case a1V2CompanyID, platformDemoV2CompanyID, strings.ToLower(billing.A1LegacyCompanyID):
return true
}
return false
}
// CompanyOmitsSEOMetaLookup is the shared A1/Demo SEO-omit decision used by V1 process
// and catalog list/get/update. Matches hardcoded companies.id, A1 cohort (legacy id),
// Platform Demo by name, or A1-style --- Title --- / --- Description --- / --- Meta ---
// category prompts.
func CompanyOmitsSEOMetaLookup(companyID uuid.UUID, legacyID, name string, hasA1SectionPrompts bool) bool {
if CompanyOmitsSEOMetaID(companyID) {
return true
}
if billing.IsA1CohortCompany(legacyID, "") {
return true
}
if strings.EqualFold(strings.TrimSpace(name), "Platform Demo") {
return true
}
return hasA1SectionPrompts
}
// CompanyOmitsSEOMeta is true when catalog/V1 process must omit meta_title /
// meta_description for this company (same rules as CompanyOmitsSEOMetaLookup).
func CompanyOmitsSEOMeta(ctx context.Context, pool *pgxpool.Pool, companyID uuid.UUID) bool {
if CompanyOmitsSEOMetaID(companyID) {
return true
}
if pool == nil {
return false
}
var legacy, name string
err := p.Pool.QueryRow(ctx, `
err := pool.QueryRow(ctx, `
SELECT COALESCE(legacy_company_id, ''), COALESCE(name, '')
FROM companies
WHERE id = $1`, companyID).Scan(&legacy, &name)
if err != nil {
return false
}
if billing.IsA1CohortCompany(legacy, "") {
return true
}
if strings.EqualFold(strings.TrimSpace(name), "Platform Demo") {
if CompanyOmitsSEOMetaLookup(companyID, legacy, name, false) {
return true
}
var hasA1Prompts bool
err = p.Pool.QueryRow(ctx, `
err = pool.QueryRow(ctx, `
SELECT EXISTS (
SELECT 1 FROM categories
WHERE company_id = $1
AND prompt ILIKE '%--- Title ---%'
AND prompt ILIKE '%--- Description ---%'
AND prompt ILIKE '%--- Meta ---%'
AND prompt ILIKE '%--- Title ---%'
AND prompt ILIKE '%--- Description ---%'
AND prompt ILIKE '%--- Meta ---%'
LIMIT 1
)`, companyID).Scan(&hasA1Prompts)
if err != nil {
@@ -521,6 +562,34 @@ func companyOmitsSEOMeta(ctx context.Context, p *Pipeline, companyID uuid.UUID)
return hasA1Prompts
}
// StripV1SEOMeta removes meta_title / meta_description keys (omit, not null).
func StripV1SEOMeta(items []V1ProcessJobItem) []V1ProcessJobItem {
for _, item := range items {
if item == nil {
continue
}
delete(item, "meta_title")
delete(item, "meta_description")
}
return items
}
// ApplyV1SEOMetaPolicy omits SEO meta on A1/Demo process items by companies.id.
func ApplyV1SEOMetaPolicy(companyID uuid.UUID, items []V1ProcessJobItem) []V1ProcessJobItem {
if !CompanyOmitsSEOMetaID(companyID) {
return items
}
return StripV1SEOMeta(items)
}
// companyOmitsSEOMeta is the pipeline wrapper around CompanyOmitsSEOMeta.
func companyOmitsSEOMeta(ctx context.Context, p *Pipeline, companyID uuid.UUID) bool {
if p == nil {
return CompanyOmitsSEOMeta(ctx, nil, companyID)
}
return CompanyOmitsSEOMeta(ctx, p.Pool, companyID)
}
func derefStringPtr(s *string) string {
if s == nil {
return ""