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:
2026-08-09 22:47:43 +02:00
commit 8580c996c3
1285 changed files with 325780 additions and 0 deletions
@@ -0,0 +1,170 @@
package catalog
import (
"context"
"encoding/json"
"github.com/google/uuid"
)
type ecommerceGroupDef struct {
Key string
Name string
Description string
Order int
}
type ecommerceFieldDef struct {
Key string
Name string
Type string
GroupKey string
Required bool
Enabled bool
Recommended bool
Unit string
DefaultValue string
SortOrder int
Hints []string
Description string
}
func ecommerceGroups() []ecommerceGroupDef {
return []ecommerceGroupDef{
{Key: "basic", Name: "Basic Information", Description: "Core product identifiers and content", Order: 10},
{Key: "pricing", Name: "Pricing", Description: "Price and currency fields", Order: 20},
{Key: "media", Name: "Media", Description: "Images and media URLs", Order: 30},
{Key: "taxonomy", Name: "Taxonomy", Description: "Categories and classification", Order: 40},
{Key: "inventory", Name: "Inventory", Description: "Stock and availability", Order: 50},
{Key: "attributes", Name: "Attributes", Description: "Variant and product attributes", Order: 60},
{Key: "shipping", Name: "Shipping", Description: "Weight and dimensions", Order: 70},
}
}
func ecommerceFields() []ecommerceFieldDef {
return []ecommerceFieldDef{
{Key: "gtin", Name: "GTIN/EAN", Type: "string", GroupKey: "basic", Required: true, Enabled: true, Recommended: true, SortOrder: 10, Hints: []string{"ean", "upc", "barcode", "gtin13"}, Description: "Product barcode"},
{Key: "title", Name: "Product name", Type: "string", GroupKey: "basic", Required: true, Enabled: true, Recommended: true, SortOrder: 20, Hints: []string{"name", "product_name", "product_title"}, Description: "Primary product title"},
{Key: "brand", Name: "Brand", Type: "string", GroupKey: "basic", Required: true, Enabled: true, Recommended: true, SortOrder: 30, Hints: []string{"manufacturer", "vendor"}, Description: "Brand or manufacturer"},
{Key: "description", Name: "Description", Type: "string", GroupKey: "basic", Required: false, Enabled: true, Recommended: true, SortOrder: 40, Hints: []string{"desc", "body", "long_description"}, Description: "Product description"},
{Key: "sku", Name: "SKU", Type: "string", GroupKey: "basic", Required: false, Enabled: true, Recommended: true, SortOrder: 50, Hints: []string{"item_sku", "article_number"}, Description: "Stock keeping unit"},
{Key: "mpn", Name: "MPN", Type: "string", GroupKey: "basic", Required: false, Enabled: true, Recommended: true, SortOrder: 60, Hints: []string{"manufacturer_part_number", "part_number"}, Description: "Manufacturer part number"},
{Key: "product_model", Name: "Product model", Type: "string", GroupKey: "basic", Required: false, Enabled: true, Recommended: true, SortOrder: 65, Hints: []string{"model", "productmodel", "product_model"}, Description: "Manufacturer model name/number"},
{Key: "product_url", Name: "Product URL", Type: "url", GroupKey: "basic", Required: false, Enabled: true, Recommended: true, SortOrder: 70, Hints: []string{"url", "link", "product_link"}, Description: "Canonical product page URL"},
{Key: "official_link", Name: "Official link", Type: "url", GroupKey: "basic", Required: false, Enabled: true, Recommended: true, SortOrder: 80, Hints: []string{"officiallink", "manufacturer_url"}, Description: "Manufacturer or brand product page"},
{Key: "price", Name: "Price", Type: "number", GroupKey: "pricing", Required: false, Enabled: true, Recommended: true, SortOrder: 10, Unit: "EUR", Hints: []string{"regular_price", "list_price", "amount"}, Description: "Regular price"},
{Key: "sale_price", Name: "Sale price", Type: "number", GroupKey: "pricing", Required: false, Enabled: true, Recommended: true, SortOrder: 20, Unit: "EUR", Hints: []string{"special_price", "discount_price"}, Description: "Promotional price"},
{Key: "purchase_price", Name: "Purchase price", Type: "number", GroupKey: "pricing", Required: false, Enabled: true, Recommended: true, SortOrder: 25, Unit: "EUR", Hints: []string{"purchaseprice", "cost", "buy_price"}, Description: "Cost / buy price"},
{Key: "currency", Name: "Currency", Type: "string", GroupKey: "pricing", Required: false, Enabled: true, Recommended: true, SortOrder: 30, DefaultValue: "EUR", Hints: []string{"price_currency", "curr"}, Description: "ISO currency code"},
{Key: "image_url", Name: "Image URL", Type: "image", GroupKey: "media", Required: false, Enabled: true, Recommended: true, SortOrder: 10, Hints: []string{"image", "image_link", "thumbnail"}, Description: "Primary product image"},
{Key: "main_image", Name: "Main image", Type: "image", GroupKey: "media", Required: true, Enabled: true, Recommended: true, SortOrder: 15, Hints: []string{"mainimage", "image", "image_url"}, Description: "Main gallery image URL"},
{Key: "additional_image_urls", Name: "Additional images", Type: "image", GroupKey: "media", Required: false, Enabled: true, Recommended: true, SortOrder: 20, Hints: []string{"images", "gallery", "moreimages", "additional_images"}, Description: "Extra product images"},
{Key: "video_url", Name: "Video URL", Type: "url", GroupKey: "media", Required: false, Enabled: true, Recommended: false, SortOrder: 30, Hints: []string{"videourl", "video"}, Description: "Product video URL"},
{Key: "category", Name: "Category", Type: "string", GroupKey: "taxonomy", Required: false, Enabled: true, Recommended: true, SortOrder: 10, Hints: []string{"product_type", "google_product_category", "category_path"}, Description: "Product category"},
{Key: "availability", Name: "Availability", Type: "string", GroupKey: "inventory", Required: false, Enabled: true, Recommended: true, SortOrder: 10, Hints: []string{"in_stock", "stock_status", "stockstatus"}, Description: "Availability status"},
{Key: "stock", Name: "Stock", Type: "number", GroupKey: "inventory", Required: false, Enabled: true, Recommended: true, SortOrder: 20, Hints: []string{"quantity", "qty", "inventory"}, Description: "Stock quantity"},
{Key: "color", Name: "Color", Type: "color", GroupKey: "attributes", Required: false, Enabled: true, Recommended: true, SortOrder: 10, Hints: []string{"colour", "farbe"}, Description: "Color attribute"},
{Key: "size", Name: "Size", Type: "string", GroupKey: "attributes", Required: false, Enabled: true, Recommended: true, SortOrder: 20, Hints: []string{"groesse", "dimension_size"}, Description: "Size attribute"},
{Key: "material", Name: "Material", Type: "string", GroupKey: "attributes", Required: false, Enabled: true, Recommended: false, SortOrder: 30, Hints: []string{"fabric", "composition"}, Description: "Material attribute"},
{Key: "warranty", Name: "Warranty", Type: "string", GroupKey: "attributes", Required: false, Enabled: true, Recommended: false, SortOrder: 40, Hints: []string{"guarantee"}, Description: "Warranty term or text"},
{Key: "service", Name: "Service", Type: "string", GroupKey: "attributes", Required: false, Enabled: true, Recommended: false, SortOrder: 50, Hints: []string{}, Description: "Service or support notes"},
{Key: "specifications", Name: "Specifications", Type: "string", GroupKey: "attributes", Required: false, Enabled: true, Recommended: true, SortOrder: 60, Hints: []string{"specs", "specification"}, Description: "Technical specifications"},
{Key: "eprel_id", Name: "EPREL ID", Type: "string", GroupKey: "attributes", Required: false, Enabled: true, Recommended: true, SortOrder: 70, Hints: []string{"eprelid", "eprel"}, Description: "EU energy label identifier"},
{Key: "weight", Name: "Weight", Type: "weight", GroupKey: "shipping", Required: false, Enabled: true, Recommended: false, SortOrder: 10, Unit: "kg", Hints: []string{"shipping_weight", "product_weight", "netmass"}, Description: "Product weight"},
{Key: "net_depth", Name: "Net depth", Type: "dimension", GroupKey: "shipping", Required: false, Enabled: true, Recommended: false, SortOrder: 20, Hints: []string{"netdepth", "depth"}, Description: "Net depth"},
{Key: "net_height", Name: "Net height", Type: "dimension", GroupKey: "shipping", Required: false, Enabled: true, Recommended: false, SortOrder: 30, Hints: []string{"netheight", "height"}, Description: "Net height"},
{Key: "net_width", Name: "Net width", Type: "dimension", GroupKey: "shipping", Required: false, Enabled: true, Recommended: false, SortOrder: 40, Hints: []string{"netwidth", "width"}, Description: "Net width"},
{Key: "net_mass", Name: "Net mass", Type: "weight", GroupKey: "shipping", Required: false, Enabled: true, Recommended: false, SortOrder: 50, Hints: []string{"netmass", "mass"}, Description: "Net mass"},
}
}
func recommendedEcommerceKeys() []string {
out := make([]string, 0)
for _, f := range ecommerceFields() {
if f.Recommended {
out = append(out, f.Key)
}
}
return out
}
func (s *Service) ensureGroupID(ctx context.Context, companyID uuid.UUID, g ecommerceGroupDef) (uuid.UUID, error) {
var id uuid.UUID
err := s.Pool.QueryRow(ctx, `
SELECT id FROM field_groups WHERE company_id = $1 AND name = $2 LIMIT 1`,
companyID, g.Name).Scan(&id)
if err == nil {
_, _ = s.Pool.Exec(ctx, `
UPDATE field_groups SET description = $3, "order" = $4, is_system = true, updated_at = now()
WHERE id = $1 AND company_id = $2`, id, companyID, g.Description, g.Order)
return id, nil
}
err = s.Pool.QueryRow(ctx, `
INSERT INTO field_groups (company_id, name, description, "order", is_system)
VALUES ($1, $2, $3, $4, true) RETURNING id`,
companyID, g.Name, g.Description, g.Order).Scan(&id)
return id, err
}
// EnsureEcommerceCatalog upserts system field groups and standard fields for ecommerce.
func (s *Service) EnsureEcommerceCatalog(ctx context.Context, companyID uuid.UUID) error {
groupIDs := map[string]uuid.UUID{}
for _, g := range ecommerceGroups() {
id, err := s.ensureGroupID(ctx, companyID, g)
if err != nil {
return err
}
groupIDs[g.Key] = id
}
for _, f := range ecommerceFields() {
gid, ok := groupIDs[f.GroupKey]
if !ok {
continue
}
hints, _ := json.Marshal(f.Hints)
var defVal *string
if f.DefaultValue != "" {
v := f.DefaultValue
defVal = &v
}
var unit *string
if f.Unit != "" {
u := f.Unit
unit = &u
}
var desc *string
if f.Description != "" {
d := f.Description
desc = &d
}
_, err := s.Pool.Exec(ctx, `
INSERT INTO standard_fields (
company_id, name, key, type, group_id, is_required, description, default_value,
is_system, enabled, unit, sort_order, mapping_hints
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,true,$9,$10,$11,$12::jsonb)
ON CONFLICT (company_id, key) DO UPDATE SET
name = EXCLUDED.name,
type = EXCLUDED.type,
group_id = EXCLUDED.group_id,
is_required = standard_fields.is_required OR EXCLUDED.is_required,
description = COALESCE(EXCLUDED.description, standard_fields.description),
default_value = COALESCE(standard_fields.default_value, EXCLUDED.default_value),
unit = COALESCE(standard_fields.unit, EXCLUDED.unit),
sort_order = EXCLUDED.sort_order,
enabled = standard_fields.enabled OR EXCLUDED.enabled,
mapping_hints = CASE
WHEN standard_fields.mapping_hints IS NULL
OR standard_fields.mapping_hints = '[]'::jsonb
THEN EXCLUDED.mapping_hints
ELSE standard_fields.mapping_hints
END,
updated_at = now()`,
companyID, f.Name, f.Key, f.Type, gid, f.Required, desc, defVal,
f.Enabled, unit, f.SortOrder, string(hints))
if err != nil {
return err
}
}
return nil
}