almost all ready

This commit is contained in:
2026-05-14 01:48:15 +02:00
parent 1b53c1c199
commit d55b0e2914
29 changed files with 5489 additions and 650 deletions
+285
View File
@@ -0,0 +1,285 @@
package handlers
import (
"context"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/labstack/echo/v4"
appmiddleware "git.ma-al.com/goc_marek/ps_shop/internal/http/middleware"
pscart "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cart"
pscookie "git.ma-al.com/goc_marek/ps_shop/internal/prestashop/cookie"
)
type cartSessionService interface {
RefreshExpiry(ctx context.Context, session *pscookie.SessionContext) error
ResolveCookiePath(ctx context.Context, req *http.Request) (string, error)
}
type CartHandler struct {
carts *pscart.Service
codec pscookie.Codec
sessions cartSessionService
}
func NewCartHandler(carts *pscart.Service, codec pscookie.Codec, sessions cartSessionService) *CartHandler {
return &CartHandler{
carts: carts,
codec: codec,
sessions: sessions,
}
}
func (h *CartHandler) Handle(c echo.Context) error {
if h == nil || h.carts == nil || h.codec == nil || h.sessions == nil {
return echo.NewHTTPError(http.StatusInternalServerError, "cart handler is not initialized")
}
session := appmiddleware.GetSession(c)
action := cartActionFromRequest(c)
input, err := cartMutationInput(c, session)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
var result *pscart.MutationResult
switch action {
case cartActionDelete:
result, err = h.carts.DeleteProduct(c.Request().Context(), input)
case cartActionUpdate:
result, err = h.carts.UpdateProduct(c.Request().Context(), input)
default:
result, err = h.carts.AddProduct(c.Request().Context(), input)
}
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "cart mutation failed: "+err.Error())
}
syncSessionCartID(session, result.CartID)
if err := h.writeSessionCookie(c, session); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "cart cookie update failed: "+err.Error())
}
if wantsJSON(c.Request()) {
return c.JSON(http.StatusOK, result)
}
return c.Redirect(http.StatusSeeOther, cartRedirectTarget(c.Request()))
}
type cartAction string
const (
cartActionAdd cartAction = "add"
cartActionUpdate cartAction = "update"
cartActionDelete cartAction = "delete"
)
func cartActionFromRequest(c echo.Context) cartAction {
switch c.Request().Method {
case http.MethodDelete:
return cartActionDelete
case http.MethodPut, http.MethodPatch:
return cartActionUpdate
}
action := strings.ToLower(strings.TrimSpace(c.FormValue("action")))
switch action {
case string(cartActionDelete):
return cartActionDelete
case string(cartActionUpdate):
return cartActionUpdate
}
switch {
case c.FormValue("delete") != "":
return cartActionDelete
case c.FormValue("update") != "":
return cartActionUpdate
default:
return cartActionAdd
}
}
func cartMutationInput(c echo.Context, session *pscookie.SessionContext) (pscart.MutationInput, error) {
productID, err := formInt64(c, "id_product")
if err != nil || productID == 0 {
return pscart.MutationInput{}, fmt.Errorf("valid id_product is required")
}
quantity := int64(1)
if raw := strings.TrimSpace(c.FormValue("qty")); raw != "" {
quantity, err = strconv.ParseInt(raw, 10, 64)
if err != nil || quantity < 0 {
return pscart.MutationInput{}, fmt.Errorf("valid qty is required")
}
}
productAttributeID, err := firstFormInt64(c, "id_product_attribute", "ipa")
if err != nil {
return pscart.MutationInput{}, fmt.Errorf("valid id_product_attribute is required")
}
customizationID, err := firstFormInt64(c, "id_customization", "customization_id")
if err != nil {
return pscart.MutationInput{}, fmt.Errorf("valid id_customization is required")
}
return pscart.MutationInput{
CartID: int64Value(session.CartID),
ProductID: productID,
ProductAttributeID: productAttributeID,
CustomizationID: customizationID,
Quantity: quantity,
CustomerID: int64Value(session.CustomerID),
GuestID: int64Value(session.GuestID),
LanguageID: int64Value(session.LanguageID),
CurrencyID: int64Value(session.CurrencyID),
ShopID: int64Value(session.ShopID),
}, nil
}
func formInt64(c echo.Context, key string) (int64, error) {
raw := strings.TrimSpace(c.FormValue(key))
if raw == "" {
return 0, nil
}
return strconv.ParseInt(raw, 10, 64)
}
func firstFormInt64(c echo.Context, keys ...string) (int64, error) {
for _, key := range keys {
value, err := formInt64(c, key)
if err != nil {
return 0, err
}
if value != 0 {
return value, nil
}
}
return 0, nil
}
func syncSessionCartID(session *pscookie.SessionContext, cartID int64) {
if session == nil || cartID == 0 {
return
}
if session.Values == nil {
session.Values = map[string]string{}
}
session.CartID = int64Ptr(cartID)
session.Values["id_cart"] = strconv.FormatInt(cartID, 10)
session.OrderedKeys = appendOrderedKeyIfMissing(session.OrderedKeys, "id_cart")
session.OrderedKeys = moveOrderedKeyToEnd(session.OrderedKeys, "checksum")
session.Plaintext = ""
session.RawCookie = ""
}
func appendOrderedKeyIfMissing(keys []string, key string) []string {
for _, existing := range keys {
if existing == key {
return keys
}
}
return append(keys, key)
}
func moveOrderedKeyToEnd(keys []string, key string) []string {
for i, existing := range keys {
if existing == key {
keys = append(keys[:i], keys[i+1:]...)
return append(keys, key)
}
}
return keys
}
func int64Ptr(value int64) *int64 {
if value == 0 {
return nil
}
v := value
return &v
}
func int64Value(value *int64) int64 {
if value == nil {
return 0
}
return *value
}
func (h *CartHandler) writeSessionCookie(c echo.Context, session *pscookie.SessionContext) error {
if err := h.sessions.RefreshExpiry(c.Request().Context(), session); err != nil {
return err
}
cookiePath, err := h.sessions.ResolveCookiePath(c.Request().Context(), c.Request())
if err != nil {
return err
}
encoded, err := h.codec.Encode(session)
if err != nil {
return err
}
session.RawCookie = encoded
writeSessionCookie(c.Request(), c.Response(), session, session.CookieName, encoded, cookiePath)
return nil
}
func writeSessionCookie(req *http.Request, res *echo.Response, session *pscookie.SessionContext, name, value, path string) {
maxAge := 1
if session != nil && session.ExpiresAt != nil {
maxAge = int(session.ExpiresAt.UTC().Unix())
}
if strings.TrimSpace(path) == "" {
path = "/"
}
header := fmt.Sprintf("%s=%s; path=%s; max-age=%d; HttpOnly; SameSite=Lax", name, value, path, maxAge)
if requestCookieSecure(req) {
header += "; Secure"
}
res.Header().Add(echo.HeaderSetCookie, header)
}
func requestCookieSecure(req *http.Request) bool {
if req == nil {
return false
}
if req.TLS != nil {
return true
}
forwarded := req.Header.Get("X-Forwarded-Proto")
if strings.Contains(forwarded, ",") {
forwarded = strings.TrimSpace(strings.Split(forwarded, ",")[0])
}
return strings.EqualFold(forwarded, "https")
}
func wantsJSON(req *http.Request) bool {
if req == nil {
return false
}
return strings.Contains(strings.ToLower(req.Header.Get(echo.HeaderAccept)), "application/json")
}
func cartRedirectTarget(req *http.Request) string {
if req == nil {
return "/cart?action=show"
}
referer := strings.TrimSpace(req.Referer())
if referer != "" {
return referer
}
path := requestLanguagePrefix(req) + "/cart"
if path == "/cart" {
return "/cart?action=show"
}
return path + "?action=show"
}