lang
This commit is contained in:
@@ -8,44 +8,43 @@ import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
appmiddleware "prestaproxy/internal/http/middleware"
|
||||
pscart "prestaproxy/internal/prestashop/cart"
|
||||
pscatalog "prestaproxy/internal/prestashop/catalog"
|
||||
psconfig "prestaproxy/internal/prestashop/config"
|
||||
pscustomer "prestaproxy/internal/prestashop/customer"
|
||||
psroutes "prestaproxy/internal/prestashop/routes"
|
||||
"prestaproxy/internal/render"
|
||||
"prestaproxy/internal/viewmodel"
|
||||
appmiddleware "git.ma-al.com/goc_marek/ps_shop/internal/http/middleware"
|
||||
pscart "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cart"
|
||||
pscatalog "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/catalog"
|
||||
psconfig "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/config"
|
||||
pscustomer "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/customer"
|
||||
psroutes "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/routes"
|
||||
"git.ma-al.com/goc_marek/ps_shop/internal/render"
|
||||
"git.ma-al.com/goc_marek/ps_shop/internal/viewmodel"
|
||||
)
|
||||
|
||||
const categorySlugContextKey = "category_slug"
|
||||
const categoryIDContextKey = "category_id"
|
||||
|
||||
type CategoryHandler struct {
|
||||
catalog *pscatalog.Service
|
||||
customers *pscustomer.Service
|
||||
carts *pscart.Service
|
||||
renderer *render.Engine
|
||||
config psconfig.Config
|
||||
products *psroutes.ProductRoute
|
||||
catalog *pscatalog.Service
|
||||
customers *pscustomer.Service
|
||||
carts *pscart.Service
|
||||
renderer *render.Engine
|
||||
config psconfig.Config
|
||||
products *psroutes.ProductRoute
|
||||
categories *psroutes.CategoryRoute
|
||||
}
|
||||
|
||||
func NewCategoryHandler(catalog *pscatalog.Service, customers *pscustomer.Service, carts *pscart.Service, renderer *render.Engine, cfg psconfig.Config, products *psroutes.ProductRoute) *CategoryHandler {
|
||||
func NewCategoryHandler(catalog *pscatalog.Service, customers *pscustomer.Service, carts *pscart.Service, renderer *render.Engine, cfg psconfig.Config, products *psroutes.ProductRoute, categories *psroutes.CategoryRoute) *CategoryHandler {
|
||||
return &CategoryHandler{
|
||||
catalog: catalog,
|
||||
customers: customers,
|
||||
carts: carts,
|
||||
renderer: renderer,
|
||||
config: cfg,
|
||||
products: products,
|
||||
catalog: catalog,
|
||||
customers: customers,
|
||||
carts: carts,
|
||||
renderer: renderer,
|
||||
config: cfg,
|
||||
products: products,
|
||||
categories: categories,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *CategoryHandler) Show(c echo.Context) error {
|
||||
session := appmiddleware.GetSession(c)
|
||||
if session == nil {
|
||||
session = appmiddleware.GetSession(c)
|
||||
}
|
||||
if h == nil || h.catalog == nil || h.renderer == nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "category handler is not initialized")
|
||||
}
|
||||
@@ -91,6 +90,16 @@ func (h *CategoryHandler) Show(c echo.Context) error {
|
||||
ShopBaseURL: h.config.PrestaShopBaseURL,
|
||||
}
|
||||
assignCategoryProductLinks(c.Request(), h.products, &page)
|
||||
menu, err := loadMenu(c.Request(), h.catalog, h.categories, languageID, shopID)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "menu query failed: "+err.Error())
|
||||
}
|
||||
page.Menu = menu
|
||||
locale, err := loadHeaderLocale(c.Request(), h.catalog, session, languageID)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "locale query failed: "+err.Error())
|
||||
}
|
||||
page.Locale = locale
|
||||
|
||||
if err := h.renderer.Category(c.Response(), c.Request(), page); err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "category render failed: "+err.Error())
|
||||
@@ -141,22 +150,3 @@ func assignCategoryProductLinks(req *http.Request, route *psroutes.ProductRoute,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func requestLanguagePrefix(req *http.Request) string {
|
||||
if req == nil || req.URL == nil {
|
||||
return ""
|
||||
}
|
||||
path := strings.Trim(req.URL.Path, "/")
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
first := path
|
||||
if idx := strings.IndexByte(path, '/'); idx >= 0 {
|
||||
first = path[:idx]
|
||||
}
|
||||
first = strings.TrimSpace(first)
|
||||
if len(first) < 2 || len(first) > 5 {
|
||||
return ""
|
||||
}
|
||||
return "/" + first
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
pscatalog "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/catalog"
|
||||
pscookie "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cookie"
|
||||
psroutes "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/routes"
|
||||
)
|
||||
|
||||
func loadMenu(req *http.Request, catalog *pscatalog.Service, route *psroutes.CategoryRoute, languageID int64, shopID int64) ([]pscatalog.MenuItem, error) {
|
||||
if catalog == nil || route == nil {
|
||||
return nil, nil
|
||||
}
|
||||
menu, err := catalog.GetCategoryMenu(req.Context(), languageID, shopID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
assignMenuLinks(req, route, menu)
|
||||
return menu, nil
|
||||
}
|
||||
|
||||
func assignMenuLinks(req *http.Request, route *psroutes.CategoryRoute, items []pscatalog.MenuItem) {
|
||||
langPrefix := requestLanguagePrefix(req)
|
||||
for i := range items {
|
||||
items[i].URL = route.BuildPath(psroutes.CategoryURLData{
|
||||
ID: items[i].ID,
|
||||
Slug: items[i].Slug,
|
||||
LanguagePrefix: langPrefix,
|
||||
})
|
||||
if len(items[i].Children) > 0 {
|
||||
assignMenuLinks(req, route, items[i].Children)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadHeaderLocale(req *http.Request, catalog *pscatalog.Service, session *pscookie.SessionContext, languageID int64) (pscatalog.HeaderLocaleData, error) {
|
||||
if catalog == nil || req == nil {
|
||||
return pscatalog.HeaderLocaleData{}, nil
|
||||
}
|
||||
|
||||
var currencyID int64
|
||||
countryISO := ""
|
||||
if session != nil {
|
||||
currencyID = int64Default(session.CurrencyID, 0)
|
||||
countryISO = strings.TrimSpace(session.Values["iso_code_country"])
|
||||
}
|
||||
|
||||
locale, err := catalog.GetHeaderLocale(req.Context(), languageID, currencyID, countryISO)
|
||||
if err != nil {
|
||||
return pscatalog.HeaderLocaleData{}, err
|
||||
}
|
||||
assignLanguageSwitchLinks(req, &locale)
|
||||
assignMarketSwitchLinks(req, &locale)
|
||||
return locale, nil
|
||||
}
|
||||
|
||||
func assignLanguageSwitchLinks(req *http.Request, locale *pscatalog.HeaderLocaleData) {
|
||||
if req == nil || req.URL == nil || locale == nil || len(locale.Languages) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
basePath := stripLanguagePrefix(req.URL.Path, locale.Languages)
|
||||
rawQuery := req.URL.RawQuery
|
||||
for i := range locale.Languages {
|
||||
code := strings.ToLower(strings.TrimSpace(locale.Languages[i].Code))
|
||||
path := "/" + code
|
||||
if basePath != "/" {
|
||||
path += basePath
|
||||
}
|
||||
if rawQuery != "" {
|
||||
path += "?" + rawQuery
|
||||
}
|
||||
locale.Languages[i].URL = path
|
||||
}
|
||||
}
|
||||
|
||||
func assignMarketSwitchLinks(req *http.Request, locale *pscatalog.HeaderLocaleData) {
|
||||
if req == nil || req.URL == nil || locale == nil || len(locale.Countries) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for i := range locale.Countries {
|
||||
marketCode := strings.ToUpper(strings.TrimSpace(locale.Countries[i].Code))
|
||||
if marketCode == "" || locale.Countries[i].CurrencyID == 0 {
|
||||
continue
|
||||
}
|
||||
query := req.URL.Query()
|
||||
query.Set("market", strconv.FormatInt(locale.Countries[i].ID, 10)+":"+marketCode+":"+strconv.FormatInt(locale.Countries[i].CurrencyID, 10))
|
||||
locale.Countries[i].URL = rebuildURL(req.URL.Path, query)
|
||||
}
|
||||
}
|
||||
|
||||
func requestLanguagePrefix(req *http.Request) string {
|
||||
if req == nil || req.URL == nil {
|
||||
return ""
|
||||
}
|
||||
path := strings.Trim(req.URL.Path, "/")
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
first := path
|
||||
if idx := strings.IndexByte(path, '/'); idx >= 0 {
|
||||
first = path[:idx]
|
||||
}
|
||||
first = strings.TrimSpace(first)
|
||||
if len(first) < 2 || len(first) > 5 {
|
||||
return ""
|
||||
}
|
||||
return "/" + first
|
||||
}
|
||||
|
||||
func stripLanguagePrefix(path string, languages []pscatalog.LocaleOption) string {
|
||||
if path == "" {
|
||||
return "/"
|
||||
}
|
||||
codes := make(map[string]struct{}, len(languages))
|
||||
for _, language := range languages {
|
||||
code := strings.ToLower(strings.TrimSpace(language.Code))
|
||||
if code != "" {
|
||||
codes[code] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
trimmed := strings.Trim(path, "/")
|
||||
if trimmed == "" {
|
||||
return "/"
|
||||
}
|
||||
parts := strings.Split(trimmed, "/")
|
||||
if _, ok := codes[strings.ToLower(parts[0])]; ok {
|
||||
parts = parts[1:]
|
||||
}
|
||||
if len(parts) == 0 {
|
||||
return "/"
|
||||
}
|
||||
return "/" + strings.Join(parts, "/")
|
||||
}
|
||||
|
||||
func rebuildURL(path string, query url.Values) string {
|
||||
if path == "" {
|
||||
path = "/"
|
||||
}
|
||||
encoded := query.Encode()
|
||||
if encoded == "" {
|
||||
return path
|
||||
}
|
||||
return path + "?" + encoded
|
||||
}
|
||||
@@ -8,14 +8,14 @@ import (
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
appmiddleware "prestaproxy/internal/http/middleware"
|
||||
pscart "prestaproxy/internal/prestashop/cart"
|
||||
pscatalog "prestaproxy/internal/prestashop/catalog"
|
||||
psconfig "prestaproxy/internal/prestashop/config"
|
||||
pscustomer "prestaproxy/internal/prestashop/customer"
|
||||
psroutes "prestaproxy/internal/prestashop/routes"
|
||||
"prestaproxy/internal/render"
|
||||
"prestaproxy/internal/viewmodel"
|
||||
appmiddleware "git.ma-al.com/goc_marek/ps_shop/internal/http/middleware"
|
||||
pscart "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cart"
|
||||
pscatalog "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/catalog"
|
||||
psconfig "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/config"
|
||||
pscustomer "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/customer"
|
||||
psroutes "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/routes"
|
||||
"git.ma-al.com/goc_marek/ps_shop/internal/render"
|
||||
"git.ma-al.com/goc_marek/ps_shop/internal/viewmodel"
|
||||
)
|
||||
|
||||
const productSlugContextKey = "product_slug"
|
||||
@@ -43,9 +43,6 @@ func NewProductHandler(products *pscatalog.Service, customers *pscustomer.Servic
|
||||
|
||||
func (h *ProductHandler) Show(c echo.Context) error {
|
||||
session := appmiddleware.GetSession(c)
|
||||
if session == nil {
|
||||
session = appmiddleware.GetSession(c)
|
||||
}
|
||||
if h == nil || h.products == nil || h.renderer == nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "product handler is not initialized")
|
||||
}
|
||||
@@ -91,6 +88,16 @@ func (h *ProductHandler) Show(c echo.Context) error {
|
||||
CartSummary: cartSummary,
|
||||
ShopBaseURL: h.config.PrestaShopBaseURL,
|
||||
}
|
||||
menu, err := loadMenu(c.Request(), h.products, h.categories, languageID, shopID)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "menu query failed: "+err.Error())
|
||||
}
|
||||
page.Menu = menu
|
||||
locale, err := loadHeaderLocale(c.Request(), h.products, session, languageID)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "locale query failed: "+err.Error())
|
||||
}
|
||||
page.Locale = locale
|
||||
|
||||
return h.renderer.Product(c.Response(), c.Request(), page)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"prestaproxy/internal/prestashop/cookie"
|
||||
"git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cookie"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
@@ -12,8 +13,8 @@ import (
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
psconfig "prestaproxy/internal/prestashop/config"
|
||||
pscookie "prestaproxy/internal/prestashop/cookie"
|
||||
psconfig "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/config"
|
||||
pscookie "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cookie"
|
||||
)
|
||||
|
||||
type AnonymousSessionInitializer interface {
|
||||
@@ -62,6 +63,7 @@ func Session(cfg psconfig.Config, codec pscookie.Codec, initializer AnonymousSes
|
||||
}
|
||||
if ownedRoute {
|
||||
applyRequestLanguage(session, resolveRequestLanguageID(c.Request().Context(), c.Request(), session, languageResolver))
|
||||
applyRequestMarket(session, requestMarketSelection(c.Request()))
|
||||
}
|
||||
if ownedRoute && shouldSetSessionCookie(rawCookie, session) {
|
||||
encoded, err := codec.Encode(session)
|
||||
@@ -70,6 +72,9 @@ func Session(cfg psconfig.Config, codec pscookie.Codec, initializer AnonymousSes
|
||||
}
|
||||
session.RawCookie = encoded
|
||||
setPrestaShopCookie(c.Request(), c.Response(), ownership.ProductPrefixes, cookieName, encoded)
|
||||
if redirectURL, ok := clearMarketSelectionURL(c.Request()); ok {
|
||||
return c.Redirect(http.StatusSeeOther, redirectURL)
|
||||
}
|
||||
}
|
||||
|
||||
SetSession(c, session)
|
||||
@@ -78,6 +83,12 @@ func Session(cfg psconfig.Config, codec pscookie.Codec, initializer AnonymousSes
|
||||
}
|
||||
}
|
||||
|
||||
type marketSelection struct {
|
||||
CountryID int64
|
||||
CountryISO string
|
||||
CurrencyID int64
|
||||
}
|
||||
|
||||
func resolveRequestLanguageID(ctx context.Context, req *http.Request, session *pscookie.SessionContext, resolver LanguageResolver) int64 {
|
||||
if resolver == nil {
|
||||
return 0
|
||||
@@ -177,6 +188,50 @@ func applyRequestLanguage(session *pscookie.SessionContext, languageID int64) {
|
||||
session.RawCookie = ""
|
||||
}
|
||||
|
||||
func applyRequestMarket(session *pscookie.SessionContext, selection marketSelection) {
|
||||
if session == nil || selection.CountryISO == "" || selection.CurrencyID == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
currentCountry := ""
|
||||
currentCurrency := int64(0)
|
||||
currentCountryID := int64(0)
|
||||
if session.Values != nil {
|
||||
currentCountry = strings.ToUpper(strings.TrimSpace(session.Values["iso_code_country"]))
|
||||
if session.CurrencyID != nil {
|
||||
currentCurrency = *session.CurrencyID
|
||||
}
|
||||
currentCountryID, _ = strconv.ParseInt(session.Values["id_country"], 10, 64)
|
||||
}
|
||||
if currentCountry == selection.CountryISO && currentCurrency == selection.CurrencyID && currentCountryID == selection.CountryID {
|
||||
return
|
||||
}
|
||||
|
||||
if session.Values == nil {
|
||||
session.Values = map[string]string{}
|
||||
}
|
||||
|
||||
session.CurrencyID = int64Ptr(selection.CurrencyID)
|
||||
session.Values["iso_code_country"] = selection.CountryISO
|
||||
if selection.CountryID > 0 {
|
||||
session.Values["id_country"] = strconv.FormatInt(selection.CountryID, 10)
|
||||
session.OrderedKeys = ensureOrderedKey(session.OrderedKeys, "id_country", 5)
|
||||
}
|
||||
session.Values["id_currency"] = strconv.FormatInt(selection.CurrencyID, 10)
|
||||
session.OrderedKeys = ensureOrderedKey(session.OrderedKeys, "iso_code_country", 4)
|
||||
session.OrderedKeys = ensureOrderedKey(session.OrderedKeys, "id_currency", 6)
|
||||
|
||||
if !session.IsLoggedIn {
|
||||
if checksum := anonymousSessionChecksum(session, sessionLanguageID(session)); checksum != "" {
|
||||
session.Values["checksum"] = checksum
|
||||
session.OrderedKeys = ensureOrderedKey(session.OrderedKeys, "checksum", len(session.OrderedKeys))
|
||||
}
|
||||
}
|
||||
|
||||
session.Plaintext = ""
|
||||
session.RawCookie = ""
|
||||
}
|
||||
|
||||
func sessionLanguageID(session *pscookie.SessionContext) int64 {
|
||||
if session == nil || session.LanguageID == nil {
|
||||
return 0
|
||||
@@ -237,6 +292,67 @@ func int64Ptr(value int64) *int64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
func requestMarketSelection(req *http.Request) marketSelection {
|
||||
if req == nil || req.URL == nil {
|
||||
return marketSelection{}
|
||||
}
|
||||
raw := strings.TrimSpace(req.URL.Query().Get("market"))
|
||||
if raw == "" {
|
||||
return marketSelection{}
|
||||
}
|
||||
parts := strings.Split(raw, ":")
|
||||
if len(parts) != 2 && len(parts) != 3 {
|
||||
return marketSelection{}
|
||||
}
|
||||
|
||||
selection := marketSelection{}
|
||||
var countryISO string
|
||||
var currencyValue string
|
||||
if len(parts) == 3 {
|
||||
countryID, err := strconv.ParseInt(strings.TrimSpace(parts[0]), 10, 64)
|
||||
if err != nil || countryID == 0 {
|
||||
return marketSelection{}
|
||||
}
|
||||
selection.CountryID = countryID
|
||||
countryISO = strings.ToUpper(strings.TrimSpace(parts[1]))
|
||||
currencyValue = parts[2]
|
||||
} else {
|
||||
countryISO = strings.ToUpper(strings.TrimSpace(parts[0]))
|
||||
currencyValue = parts[1]
|
||||
}
|
||||
|
||||
currencyID, err := strconv.ParseInt(strings.TrimSpace(currencyValue), 10, 64)
|
||||
if err != nil || currencyID == 0 {
|
||||
return marketSelection{}
|
||||
}
|
||||
if len(countryISO) < 2 || len(countryISO) > 5 {
|
||||
return marketSelection{}
|
||||
}
|
||||
|
||||
selection.CountryISO = countryISO
|
||||
selection.CurrencyID = currencyID
|
||||
return selection
|
||||
}
|
||||
|
||||
func clearMarketSelectionURL(req *http.Request) (string, bool) {
|
||||
if req == nil || req.URL == nil {
|
||||
return "", false
|
||||
}
|
||||
query := req.URL.Query()
|
||||
if query.Get("market") == "" {
|
||||
return "", false
|
||||
}
|
||||
query.Del("market")
|
||||
cleanPath := req.URL.Path
|
||||
if cleanPath == "" {
|
||||
cleanPath = "/"
|
||||
}
|
||||
if encoded := query.Encode(); encoded != "" {
|
||||
return cleanPath + "?" + encoded, true
|
||||
}
|
||||
return cleanPath, true
|
||||
}
|
||||
|
||||
func setPrestaShopCookie(req *http.Request, res *echo.Response, ownedPrefixes []string, name, value string) {
|
||||
http.SetCookie(res.Writer, &http.Cookie{
|
||||
Name: name,
|
||||
@@ -277,7 +393,11 @@ func requestCookieDomain(req *http.Request) string {
|
||||
return ""
|
||||
}
|
||||
if parsed, err := url.Parse("http://" + host); err == nil {
|
||||
return parsed.Hostname()
|
||||
host = parsed.Hostname()
|
||||
}
|
||||
host = strings.TrimSpace(strings.TrimPrefix(host, "."))
|
||||
if host == "" || strings.EqualFold(host, "localhost") || net.ParseIP(host) != nil {
|
||||
return ""
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
@@ -55,6 +55,32 @@ type CategoryProductCard struct {
|
||||
EAN13 string
|
||||
}
|
||||
|
||||
type MenuItem struct {
|
||||
ID int64
|
||||
ParentID int64
|
||||
Name string
|
||||
Slug string
|
||||
Depth int
|
||||
URL string `gorm:"-"`
|
||||
Children []MenuItem `gorm:"-"`
|
||||
}
|
||||
|
||||
type LocaleOption struct {
|
||||
ID int64
|
||||
CurrencyID int64 `gorm:"column:currency_id"`
|
||||
Label string
|
||||
Code string
|
||||
Meta string
|
||||
URL string `gorm:"-"`
|
||||
}
|
||||
|
||||
type HeaderLocaleData struct {
|
||||
CurrentLanguage LocaleOption
|
||||
CurrentCountry LocaleOption
|
||||
Languages []LocaleOption
|
||||
Countries []LocaleOption
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
db *gorm.DB
|
||||
prefix string
|
||||
@@ -239,3 +265,237 @@ func (s *Service) ResolveLanguageID(ctx context.Context, req *http.Request, fall
|
||||
}
|
||||
return row.ID
|
||||
}
|
||||
|
||||
func (s *Service) GetCategoryMenu(ctx context.Context, languageID int64, shopID int64) ([]MenuItem, error) {
|
||||
rootCategoryID, err := s.rootCategoryID(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(`
|
||||
WITH RECURSIVE category_tree AS (
|
||||
SELECT c.id_category AS id,
|
||||
c.id_parent AS parent_id,
|
||||
cl.name AS name,
|
||||
cl.link_rewrite AS slug,
|
||||
0 AS depth
|
||||
FROM %scategory c
|
||||
JOIN %scategory_shop cs ON cs.id_category = c.id_category
|
||||
JOIN %scategory_lang cl ON cl.id_category = c.id_category
|
||||
WHERE c.id_parent = ?
|
||||
AND c.active = 1
|
||||
AND cs.id_shop = ?
|
||||
AND cl.id_lang = ?
|
||||
UNION ALL
|
||||
SELECT c.id_category AS id,
|
||||
c.id_parent AS parent_id,
|
||||
cl.name AS name,
|
||||
cl.link_rewrite AS slug,
|
||||
tree.depth + 1 AS depth
|
||||
FROM %scategory c
|
||||
JOIN %scategory_shop cs ON cs.id_category = c.id_category
|
||||
JOIN %scategory_lang cl ON cl.id_category = c.id_category
|
||||
JOIN category_tree tree ON tree.id = c.id_parent
|
||||
WHERE c.active = 1
|
||||
AND cs.id_shop = ?
|
||||
AND cl.id_lang = ?
|
||||
)
|
||||
SELECT id, parent_id, name, slug, depth
|
||||
FROM category_tree
|
||||
ORDER BY depth ASC, parent_id ASC, name ASC
|
||||
`, s.prefix, s.prefix, s.prefix, s.prefix, s.prefix, s.prefix)
|
||||
|
||||
var flat []MenuItem
|
||||
if err := s.db.WithContext(ctx).Raw(strings.TrimSpace(query), rootCategoryID, shopID, languageID, shopID, languageID).Scan(&flat).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(flat) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
nodes := make(map[int64]MenuItem, len(flat))
|
||||
childrenByParent := make(map[int64][]int64, len(flat))
|
||||
for i := range flat {
|
||||
item := flat[i]
|
||||
nodes[item.ID] = item
|
||||
childrenByParent[item.ParentID] = append(childrenByParent[item.ParentID], item.ID)
|
||||
}
|
||||
|
||||
rootIDs := childrenByParent[rootCategoryID]
|
||||
roots := make([]MenuItem, 0, len(rootIDs))
|
||||
for _, id := range rootIDs {
|
||||
if item, ok := buildMenuTree(id, nodes, childrenByParent); ok {
|
||||
roots = append(roots, item)
|
||||
}
|
||||
}
|
||||
return roots, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetHeaderLocale(ctx context.Context, languageID int64, currencyID int64, countryISO string) (HeaderLocaleData, error) {
|
||||
var locale HeaderLocaleData
|
||||
defaultCurrencyID, err := s.defaultCurrencyID(ctx)
|
||||
if err != nil {
|
||||
return HeaderLocaleData{}, err
|
||||
}
|
||||
|
||||
languageQuery := fmt.Sprintf(`
|
||||
SELECT id_lang AS id,
|
||||
name AS label,
|
||||
UPPER(iso_code) AS code,
|
||||
COALESCE(NULLIF(language_code, ''), UPPER(iso_code)) AS meta
|
||||
FROM %slang
|
||||
WHERE active = 1
|
||||
ORDER BY id_lang ASC
|
||||
`, s.prefix)
|
||||
if err := s.db.WithContext(ctx).Raw(strings.TrimSpace(languageQuery)).Scan(&locale.Languages).Error; err != nil {
|
||||
return HeaderLocaleData{}, err
|
||||
}
|
||||
|
||||
hasCountryCurrency, err := s.columnExists(ctx, s.prefix+"country", "id_currency")
|
||||
if err != nil {
|
||||
return HeaderLocaleData{}, err
|
||||
}
|
||||
|
||||
var countryQuery string
|
||||
var countryArgs []any
|
||||
if hasCountryCurrency {
|
||||
countryQuery = fmt.Sprintf(`
|
||||
SELECT c.id_country AS id,
|
||||
COALESCE(c.id_currency, ?) AS currency_id,
|
||||
cl.name AS label,
|
||||
UPPER(c.iso_code) AS code,
|
||||
TRIM(CONCAT(COALESCE(UPPER(cur.iso_code), ''), ' ', COALESCE(cur.sign, ''))) AS meta
|
||||
FROM %scountry c
|
||||
JOIN %scountry_lang cl ON cl.id_country = c.id_country
|
||||
LEFT JOIN %scurrency cur ON cur.id_currency = c.id_currency
|
||||
WHERE c.active = 1
|
||||
AND cl.id_lang = ?
|
||||
ORDER BY cl.name ASC
|
||||
`, s.prefix, s.prefix, s.prefix)
|
||||
countryArgs = []any{defaultCurrencyID, languageID}
|
||||
} else {
|
||||
countryQuery = fmt.Sprintf(`
|
||||
SELECT c.id_country AS id,
|
||||
? AS currency_id,
|
||||
cl.name AS label,
|
||||
UPPER(c.iso_code) AS code,
|
||||
UPPER(c.iso_code) AS meta
|
||||
FROM %scountry c
|
||||
JOIN %scountry_lang cl ON cl.id_country = c.id_country
|
||||
WHERE c.active = 1
|
||||
AND cl.id_lang = ?
|
||||
ORDER BY cl.name ASC
|
||||
`, s.prefix, s.prefix)
|
||||
countryArgs = []any{defaultCurrencyID, languageID}
|
||||
}
|
||||
if err := s.db.WithContext(ctx).Raw(strings.TrimSpace(countryQuery), countryArgs...).Scan(&locale.Countries).Error; err != nil {
|
||||
return HeaderLocaleData{}, err
|
||||
}
|
||||
|
||||
locale.CurrentLanguage = pickLocaleOptionByID(locale.Languages, languageID)
|
||||
locale.CurrentCountry = pickLocaleOptionByCode(locale.Countries, countryISO)
|
||||
return locale, nil
|
||||
}
|
||||
|
||||
func (s *Service) rootCategoryID(ctx context.Context) (int64, error) {
|
||||
var row struct {
|
||||
Value string `gorm:"column:value"`
|
||||
}
|
||||
query := fmt.Sprintf("SELECT value FROM %sconfiguration WHERE name = 'PS_ROOT_CATEGORY' LIMIT 1", s.prefix)
|
||||
if err := s.db.WithContext(ctx).Raw(query).Scan(&row).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
id := parseInt64(row.Value)
|
||||
if id == 0 {
|
||||
return 1, nil
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (s *Service) defaultCurrencyID(ctx context.Context) (int64, error) {
|
||||
var row struct {
|
||||
Value string `gorm:"column:value"`
|
||||
}
|
||||
query := fmt.Sprintf("SELECT value FROM %sconfiguration WHERE name = 'PS_CURRENCY_DEFAULT' LIMIT 1", s.prefix)
|
||||
if err := s.db.WithContext(ctx).Raw(query).Scan(&row).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
id := parseInt64(row.Value)
|
||||
if id == 0 {
|
||||
return 1, nil
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (s *Service) columnExists(ctx context.Context, tableName string, columnName string) (bool, error) {
|
||||
var count int64
|
||||
query := `
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = ?
|
||||
AND column_name = ?
|
||||
`
|
||||
if err := s.db.WithContext(ctx).Raw(strings.TrimSpace(query), tableName, columnName).Scan(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func parseInt64(value string) int64 {
|
||||
var n int64
|
||||
for _, r := range value {
|
||||
if r < '0' || r > '9' {
|
||||
return 0
|
||||
}
|
||||
n = n*10 + int64(r-'0')
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func buildMenuTree(id int64, nodes map[int64]MenuItem, childrenByParent map[int64][]int64) (MenuItem, bool) {
|
||||
item, ok := nodes[id]
|
||||
if !ok {
|
||||
return MenuItem{}, false
|
||||
}
|
||||
childIDs := childrenByParent[id]
|
||||
if len(childIDs) == 0 {
|
||||
return item, true
|
||||
}
|
||||
item.Children = make([]MenuItem, 0, len(childIDs))
|
||||
for _, childID := range childIDs {
|
||||
child, ok := buildMenuTree(childID, nodes, childrenByParent)
|
||||
if ok {
|
||||
item.Children = append(item.Children, child)
|
||||
}
|
||||
}
|
||||
return item, true
|
||||
}
|
||||
|
||||
func pickLocaleOptionByID(options []LocaleOption, id int64) LocaleOption {
|
||||
for _, option := range options {
|
||||
if option.ID == id && id != 0 {
|
||||
return option
|
||||
}
|
||||
}
|
||||
if len(options) > 0 {
|
||||
return options[0]
|
||||
}
|
||||
return LocaleOption{}
|
||||
}
|
||||
|
||||
func pickLocaleOptionByCode(options []LocaleOption, code string) LocaleOption {
|
||||
code = strings.ToUpper(strings.TrimSpace(code))
|
||||
for _, option := range options {
|
||||
if option.Code == code && code != "" {
|
||||
return option
|
||||
}
|
||||
}
|
||||
if len(options) > 0 {
|
||||
return options[0]
|
||||
}
|
||||
if code == "" {
|
||||
return LocaleOption{}
|
||||
}
|
||||
return LocaleOption{Code: code, Label: code, Meta: code}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
pscookie "prestaproxy/internal/prestashop/cookie"
|
||||
pscookie "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cookie"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
pscookie "prestaproxy/internal/prestashop/cookie"
|
||||
pscookie "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cookie"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -3,9 +3,9 @@ package render
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"prestaproxy/internal/assets"
|
||||
"prestaproxy/internal/viewmodel"
|
||||
"prestaproxy/templates"
|
||||
"git.ma-al.com/goc_marek/ps_shop/internal/assets"
|
||||
"git.ma-al.com/goc_marek/ps_shop/internal/viewmodel"
|
||||
"git.ma-al.com/goc_marek/ps_shop/templates"
|
||||
)
|
||||
|
||||
type Engine struct {
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
package viewmodel
|
||||
|
||||
import (
|
||||
pscart "prestaproxy/internal/prestashop/cart"
|
||||
pscatalog "prestaproxy/internal/prestashop/catalog"
|
||||
pscookie "prestaproxy/internal/prestashop/cookie"
|
||||
pscustomer "prestaproxy/internal/prestashop/customer"
|
||||
pscart "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cart"
|
||||
pscatalog "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/catalog"
|
||||
pscookie "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cookie"
|
||||
pscustomer "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/customer"
|
||||
)
|
||||
|
||||
type CategoryPageData struct {
|
||||
Category pscatalog.CategoryPageData
|
||||
Menu []pscatalog.MenuItem
|
||||
Locale pscatalog.HeaderLocaleData
|
||||
Session *pscookie.SessionContext
|
||||
Customer *pscustomer.Profile
|
||||
CartSummary *pscart.Summary
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
package viewmodel
|
||||
|
||||
import (
|
||||
pscart "prestaproxy/internal/prestashop/cart"
|
||||
pscatalog "prestaproxy/internal/prestashop/catalog"
|
||||
pscookie "prestaproxy/internal/prestashop/cookie"
|
||||
pscustomer "prestaproxy/internal/prestashop/customer"
|
||||
pscart "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cart"
|
||||
pscatalog "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/catalog"
|
||||
pscookie "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cookie"
|
||||
pscustomer "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/customer"
|
||||
)
|
||||
|
||||
type ProductPageData struct {
|
||||
Product pscatalog.ProductPageData
|
||||
CategoryURL string
|
||||
Menu []pscatalog.MenuItem
|
||||
Locale pscatalog.HeaderLocaleData
|
||||
Session *pscookie.SessionContext
|
||||
Customer *pscustomer.Profile
|
||||
CartSummary *pscart.Summary
|
||||
|
||||
Reference in New Issue
Block a user