- Handle validateContent errors in worker.go - Handle os.Remove errors in worker.go - Remove dead CheckContent function from floor_handler.go - Add comment markers for intentionally ignored errors - Remove unused result variable Co-Authored-By: Claude <noreply@anthropic.com>
197 lines
6.2 KiB
Go
197 lines
6.2 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"floorvisualizer/internal/middleware"
|
|
"floorvisualizer/internal/model"
|
|
"floorvisualizer/internal/repository"
|
|
)
|
|
|
|
type ProductItem struct {
|
|
Brand string `json:"brand"`
|
|
SeriesName string `json:"series_name"`
|
|
StyleName string `json:"style_name"`
|
|
SKU string `json:"sku"`
|
|
Category string `json:"category"`
|
|
Material string `json:"material"`
|
|
ColorTone string `json:"color_tone"`
|
|
Finish string `json:"finish"`
|
|
PricePerSqft float64 `json:"price_per_sqft"`
|
|
PriceTier string `json:"price_tier"`
|
|
MainImageURL string `json:"main_image_url"`
|
|
SizeLabel string `json:"size_label"`
|
|
CoverageSqftPerBox float64 `json:"coverage_sqft_per_box"`
|
|
IsFavorited bool `json:"is_favorited"`
|
|
SpecCount int `json:"spec_count"`
|
|
}
|
|
|
|
type ProductListResponse struct {
|
|
Products []ProductItem `json:"products"`
|
|
Total int `json:"total"`
|
|
Page int `json:"page"`
|
|
Limit int `json:"limit"`
|
|
TotalPages int `json:"total_pages"`
|
|
FilterOptions *repository.FilterOptions `json:"filter_options"`
|
|
}
|
|
|
|
var productsCache func(ctx context.Context, sku string) (*model.Product, bool)
|
|
var productsCacheSet func(ctx context.Context, sku string, product *model.Product) error
|
|
var productsTrackView func(ctx context.Context, sku string)
|
|
|
|
func SetProductsCache(
|
|
getter func(ctx context.Context, sku string) (*model.Product, bool),
|
|
setter func(ctx context.Context, sku string, product *model.Product) error,
|
|
tracker func(ctx context.Context, sku string),
|
|
) {
|
|
productsCache = getter
|
|
productsCacheSet = setter
|
|
productsTrackView = tracker
|
|
}
|
|
|
|
func Products(db *sql.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
// Detail: /products/{sku}
|
|
sku := strings.TrimPrefix(r.URL.Path, "/products/")
|
|
if sku != "" && sku != r.URL.Path {
|
|
// Track view + check cache
|
|
if productsTrackView != nil {
|
|
productsTrackView(r.Context(), sku)
|
|
}
|
|
var product *model.Product
|
|
if productsCache != nil {
|
|
if p, ok := productsCache(r.Context(), sku); ok {
|
|
product = p
|
|
}
|
|
}
|
|
if product == nil {
|
|
product, err := repository.GetProductBySKU(db, sku)
|
|
if err != nil {
|
|
writeJSON(w, 404, map[string]string{"error": "product not found"})
|
|
return
|
|
}
|
|
if productsCacheSet != nil {
|
|
productsCacheSet(r.Context(), sku, product)
|
|
}
|
|
}
|
|
// Attach variants (same style_name, different sizes)
|
|
variants, _ := repository.GetVariantsByStyle(db, sku)
|
|
if len(variants) > 0 {
|
|
product.Variants = variants
|
|
}
|
|
// Check favorite status and build specs
|
|
userID := middleware.UserID(r.Context())
|
|
favMap, _ := repository.GetFavoritedSKUs(db, userID, []string{sku}) // ok to ignore err: non-critical
|
|
product.IsFavorited = favMap[sku]
|
|
// Build specs: main product + variants as size options
|
|
allVariants := append([]model.Product{*product}, product.Variants...)
|
|
for _, v := range allVariants {
|
|
product.Specs = append(product.Specs, model.ProductSpec{
|
|
SKU: v.SKU, SizeLabel: v.SizeLabel,
|
|
WidthIn: v.WidthIn, LengthIn: v.LengthIn, Finish: v.Finish,
|
|
PricePerSqft: v.PricePerSqft, PriceTier: v.PriceTier,
|
|
CoverageSqftPerBox: v.CoverageSqftPerBox, MainImageURL: v.MainImageURL,
|
|
})
|
|
}
|
|
writeJSON(w, 200, product)
|
|
return
|
|
}
|
|
|
|
if r.Method != http.MethodGet {
|
|
writeErr(w, 405, "Method not allowed")
|
|
return
|
|
}
|
|
|
|
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
|
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
|
|
|
|
q := r.URL.Query()
|
|
params := repository.FilterParams{
|
|
SeriesName: q.Get("series_name"),
|
|
Category: q.Get("category"),
|
|
Material: q.Get("material"),
|
|
ColorTone: q.Get("color_tone"),
|
|
Finish: q.Get("finish"),
|
|
WoodSpecies: q.Get("wood_species"),
|
|
Search: q.Get("search"),
|
|
Brand: q.Get("brand"),
|
|
Page: page,
|
|
Limit: limit,
|
|
}
|
|
|
|
prods, total, err := repository.QueryFilteredProducts(db, params)
|
|
if err != nil {
|
|
writeJSON(w, 500, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
filterOpts, _ := repository.GetFilterOptions(db)
|
|
if filterOpts == nil {
|
|
filterOpts = &repository.FilterOptions{}
|
|
}
|
|
|
|
// Collect SKUs for batch favorite check
|
|
skus := make([]string, len(prods))
|
|
for i, p := range prods {
|
|
skus[i] = p.SKU
|
|
}
|
|
userID := middleware.UserID(r.Context())
|
|
favSKUs, _ := repository.GetFavoritedSKUs(db, userID, skus) // ok to ignore err: non-critical
|
|
specCounts := repository.GetSpecCounts(db, skus)
|
|
|
|
items := make([]ProductItem, 0, len(prods))
|
|
for _, p := range prods {
|
|
items = append(items, ProductItem{
|
|
Brand: p.Brand, SeriesName: p.SeriesName, StyleName: p.StyleName,
|
|
SKU: p.SKU, Category: p.Category, Material: p.Material,
|
|
ColorTone: p.ColorTone, Finish: p.Finish,
|
|
PricePerSqft: p.PricePerSqft, PriceTier: p.PriceTier,
|
|
MainImageURL: p.MainImageURL, SizeLabel: p.SizeLabel,
|
|
CoverageSqftPerBox: p.CoverageSqftPerBox,
|
|
IsFavorited: favSKUs[p.SKU],
|
|
SpecCount: specCounts[p.Brand+"|"+p.StyleName+"|"+p.SeriesName],
|
|
})
|
|
}
|
|
|
|
effectiveLimit := params.Limit
|
|
if effectiveLimit <= 0 {
|
|
effectiveLimit = 20
|
|
}
|
|
totalPages := 0
|
|
if total > 0 {
|
|
totalPages = (total + effectiveLimit - 1) / effectiveLimit
|
|
}
|
|
|
|
writeJSON(w, 200, ProductListResponse{
|
|
Products: items, Total: total, Page: params.Page,
|
|
Limit: effectiveLimit, TotalPages: totalPages, FilterOptions: filterOpts,
|
|
})
|
|
}
|
|
}
|
|
|
|
func ProductOptions(db *sql.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
opts, err := repository.GetFilterOptions(db)
|
|
if err != nil {
|
|
writeJSON(w, 500, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
writeJSON(w, 200, opts)
|
|
}
|
|
}
|
|
|
|
func FilterOptions(db *sql.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
filters, err := repository.GetFilterHierarchy(db)
|
|
if err != nil {
|
|
writeJSON(w, 500, map[string]string{"error": err.Error()})
|
|
return
|
|
}
|
|
writeJSON(w, 200, map[string]interface{}{"filters": filters})
|
|
}
|
|
}
|