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>
This commit is contained in:
86
models/catalogue.go
Normal file
86
models/catalogue.go
Normal file
@@ -0,0 +1,86 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user