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>
149 lines
3.3 KiB
Go
149 lines
3.3 KiB
Go
package controllers
|
|
|
|
import (
|
|
"errors"
|
|
"nearle/repositories"
|
|
"nearle/services"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
)
|
|
|
|
type CatalogueController struct {
|
|
catalogueService services.CatalogueService
|
|
}
|
|
|
|
func NewCatalogueController(catalogueService services.CatalogueService) *CatalogueController {
|
|
return &CatalogueController{catalogueService: catalogueService}
|
|
}
|
|
|
|
func (ctl *CatalogueController) GetBrands(c *fiber.Ctx) error {
|
|
brands, err := ctl.catalogueService.GetBrands()
|
|
if err != nil {
|
|
return c.JSON(fiber.Map{
|
|
"code": 500,
|
|
"message": "Failed to fetch catalogue brands",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": brands,
|
|
})
|
|
}
|
|
|
|
func (ctl *CatalogueController) GetCategories(c *fiber.Ctx) error {
|
|
brand := c.Query("brand")
|
|
if brand == "" {
|
|
return c.JSON(fiber.Map{
|
|
"code": 400,
|
|
"message": "brand is required",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
categories, err := ctl.catalogueService.GetCategories(brand)
|
|
if err != nil {
|
|
if errors.Is(err, repositories.ErrUnknownBrand) {
|
|
return c.JSON(fiber.Map{
|
|
"code": 400,
|
|
"message": "Unknown brand: " + brand,
|
|
"status": false,
|
|
})
|
|
}
|
|
return c.JSON(fiber.Map{
|
|
"code": 500,
|
|
"message": "Failed to fetch categories",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": categories,
|
|
})
|
|
}
|
|
|
|
func (ctl *CatalogueController) GetProducts(c *fiber.Ctx) error {
|
|
// brand is an optional filter: omitting it browses the entire catalogue
|
|
// across every brand, which is the entry point for "show everything,
|
|
// then let the store owner choose".
|
|
brand := c.Query("brand")
|
|
category := c.Query("category")
|
|
keyword := c.Query("keyword")
|
|
pageno, _ := strconv.Atoi(c.Query("pageno"))
|
|
pagesize, _ := strconv.Atoi(c.Query("pagesize"))
|
|
|
|
products, total, err := ctl.catalogueService.GetProducts(brand, category, keyword, pageno, pagesize)
|
|
if err != nil {
|
|
if errors.Is(err, repositories.ErrUnknownBrand) {
|
|
return c.JSON(fiber.Map{
|
|
"code": 400,
|
|
"message": "Unknown brand: " + brand,
|
|
"status": false,
|
|
})
|
|
}
|
|
return c.JSON(fiber.Map{
|
|
"code": 500,
|
|
"message": "Failed to fetch catalogue products",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"total": total,
|
|
"details": products,
|
|
})
|
|
}
|
|
|
|
func (ctl *CatalogueController) GetProductBySKU(c *fiber.Ctx) error {
|
|
brand := c.Query("brand")
|
|
sku := c.Query("sku")
|
|
if brand == "" || sku == "" {
|
|
return c.JSON(fiber.Map{
|
|
"code": 400,
|
|
"message": "brand and sku are required",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
product, err := ctl.catalogueService.GetProductBySKU(brand, sku)
|
|
if err != nil {
|
|
if errors.Is(err, repositories.ErrUnknownBrand) {
|
|
return c.JSON(fiber.Map{
|
|
"code": 400,
|
|
"message": "Unknown brand: " + brand,
|
|
"status": false,
|
|
})
|
|
}
|
|
return c.JSON(fiber.Map{
|
|
"code": 500,
|
|
"message": "Failed to fetch catalogue product",
|
|
"status": false,
|
|
})
|
|
}
|
|
if product == nil {
|
|
return c.JSON(fiber.Map{
|
|
"code": 404,
|
|
"message": "Product not found",
|
|
"status": false,
|
|
})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"code": http.StatusOK,
|
|
"message": "Success",
|
|
"status": true,
|
|
"details": product,
|
|
})
|
|
}
|