almost all ready
This commit is contained in:
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user