Files
backend_fiesta/models/catalogue.go
Suriya fab06bb33e Bridge global catalogue DB to per-store product catalogue
Adds a separate CatalogueDB (pgvector) connection alongside the main
nearledb, plus a new catalogue module (repository/service/controller/
routes) to browse it by brand, category, and keyword, with brand
optional so the whole ~237-product catalogue can be browsed unfiltered.

Adds the actual bridge: importing a catalogue product snapshots it into
the tenant's own products table (keyed on brand+catalogueid, since a
catalogue row's bare id is only unique within its own brand table),
then links it via the existing productlocations upsert. Re-importing
tops up stock and refreshes price instead of duplicating. Also adds an
imported-refs endpoint so the frontend can badge already-imported items
without diffing full product lists, and wires the new AWS S3 image
store used to resolve catalogue product photos.

Bumps Go/Docker to 1.24 for the AWS SDK dependency this needs.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-16 17:34:20 +05:30

87 lines
2.7 KiB
Go

package models
import (
"strings"
"time"
)
// CatalogueProduct maps to the per-brand tables (brand_dabur, brand_nestle, ...)
// in the separate pgvector catalogue database. The `embedding` vector column
// is intentionally not mapped here — it is never needed in an API response.
type CatalogueProduct struct {
ID int64 `json:"id"`
Brand string `json:"brand"`
ProductName string `json:"product_name"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
Category string `json:"category,omitempty"`
ImageID string `json:"image_id,omitempty"`
Images []string `json:"images,omitempty"`
Size string `json:"size,omitempty"`
VariantKey string `json:"variant_key,omitempty"`
ProductSKU string `json:"product_sku,omitempty"`
SKUSource string `json:"sku_source,omitempty"`
PriceRange string `json:"price_range,omitempty"`
Providers PGStringArray `json:"providers,omitempty"`
FSSAILicense string `json:"fssai_license,omitempty"`
Highlights PGStringArray `json:"highlights,omitempty"`
Nutrients PGStringArray `json:"nutrients,omitempty"`
SearchQuery string `json:"search_query,omitempty"`
CreatedAt time.Time `json:"created_at,omitempty"`
UpdatedAt time.Time `json:"updated_at,omitempty"`
}
// CatalogueBrand describes a brand available in the catalogue DB.
type CatalogueBrand struct {
Brand string `json:"brand"`
ProductCount int64 `json:"product_count"`
}
// PGStringArray is a plain []string used for catalogue array fields
// (providers, highlights, nutrients) in JSON responses.
type PGStringArray []string
// ParsePGArray parses a Postgres text[] literal (e.g. "{Amazon,Flipkart}")
// into a Go []string. GORM's raw-scan-into-struct silently drops slice-kind
// destination fields, so array columns are queried as `col::text` and
// converted with this function after scanning, rather than scanned directly.
func ParsePGArray(s string) PGStringArray {
s = strings.TrimSpace(s)
if s == "" || s == "{}" {
return PGStringArray{}
}
if strings.HasPrefix(s, "{") && strings.HasSuffix(s, "}") {
s = s[1 : len(s)-1]
}
var result PGStringArray
var buf strings.Builder
inQuotes := false
escaped := false
for _, r := range s {
switch {
case escaped:
buf.WriteRune(r)
escaped = false
case r == '\\':
escaped = true
case r == '"':
inQuotes = !inQuotes
case r == ',' && !inQuotes:
result = append(result, buf.String())
buf.Reset()
default:
buf.WriteRune(r)
}
}
result = append(result, buf.String())
for i, v := range result {
if v == "NULL" {
result[i] = ""
}
}
return result
}