This commit is contained in:
2026-05-12 05:08:39 +02:00
parent ee054955b9
commit c99065496b
28 changed files with 2045 additions and 120 deletions
+33 -43
View File
@@ -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
}
+151
View File
@@ -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
}
+18 -11
View File
@@ -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 -1
View File
@@ -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"
)
+123 -3
View File
@@ -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
}