Some checks failed
deploy / deploy (push) Failing after 8m33s
- Add Hammer library for rate limiting with ETS backend - Rate limit login (5/min), magic link (3/min), newsletter (10/min), API (60/min) - Add themed 429 error page using bare shop styling - Enable HSTS in production with rewrite_on for Fly proxy - Add security hardening plan to docs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
111 lines
2.8 KiB
Elixir
111 lines
2.8 KiB
Elixir
defmodule BerrypodWeb.CartController do
|
|
@moduledoc """
|
|
Cart controller handling both JSON API persistence and HTML form fallbacks.
|
|
|
|
The JSON `update/2` action is called by a JS hook after each LiveView cart
|
|
change. The HTML actions (`add/2`, `remove/2`, `update_item/2`) provide
|
|
no-JS fallbacks via plain form POST + redirect.
|
|
"""
|
|
|
|
use BerrypodWeb, :controller
|
|
|
|
alias Berrypod.Cart
|
|
|
|
plug BerrypodWeb.Plugs.RateLimit, [type: :api] when action == :update
|
|
|
|
@doc """
|
|
Updates the cart in session (JSON API for JS hook).
|
|
"""
|
|
def update(conn, %{"items" => items}) when is_list(items) do
|
|
cart_items = Cart.deserialize(items)
|
|
|
|
conn
|
|
|> Cart.put_in_session(cart_items)
|
|
|> json(%{ok: true})
|
|
end
|
|
|
|
def update(conn, _params) do
|
|
conn
|
|
|> put_status(:bad_request)
|
|
|> json(%{error: "Invalid cart data"})
|
|
end
|
|
|
|
@doc """
|
|
Adds an item to cart via form POST (no-JS fallback).
|
|
"""
|
|
def add(conn, %{"variant_id" => variant_id, "quantity" => qty_str}) do
|
|
quantity = parse_quantity(qty_str)
|
|
cart = Cart.get_from_session(get_session(conn))
|
|
cart = Cart.add_item(cart, variant_id, quantity)
|
|
|
|
conn
|
|
|> Cart.put_in_session(cart)
|
|
|> put_flash(:info, "Added to basket")
|
|
|> redirect(to: ~p"/cart")
|
|
end
|
|
|
|
def add(conn, _params) do
|
|
conn
|
|
|> put_flash(:error, "Could not add item to basket")
|
|
|> redirect(to: ~p"/cart")
|
|
end
|
|
|
|
@doc """
|
|
Removes an item from cart via form POST (no-JS fallback).
|
|
"""
|
|
def remove(conn, %{"variant_id" => variant_id}) do
|
|
cart = Cart.get_from_session(get_session(conn))
|
|
cart = Cart.remove_item(cart, variant_id)
|
|
|
|
conn
|
|
|> Cart.put_in_session(cart)
|
|
|> put_flash(:info, "Removed from basket")
|
|
|> redirect(to: ~p"/cart")
|
|
end
|
|
|
|
@doc """
|
|
Updates item quantity via form POST (no-JS fallback).
|
|
"""
|
|
def update_item(conn, %{"variant_id" => variant_id, "quantity" => qty_str}) do
|
|
quantity = parse_update_quantity(qty_str)
|
|
cart = Cart.get_from_session(get_session(conn))
|
|
cart = Cart.update_quantity(cart, variant_id, quantity)
|
|
|
|
conn
|
|
|> Cart.put_in_session(cart)
|
|
|> redirect(to: ~p"/cart")
|
|
end
|
|
|
|
@doc """
|
|
Updates delivery country via form POST (no-JS fallback).
|
|
"""
|
|
def update_country(conn, %{"country" => code}) when is_binary(code) and code != "" do
|
|
conn
|
|
|> put_session("country_code", code)
|
|
|> redirect(to: ~p"/cart")
|
|
end
|
|
|
|
def update_country(conn, _params) do
|
|
redirect(conn, to: ~p"/cart")
|
|
end
|
|
|
|
defp parse_quantity(str) when is_binary(str) do
|
|
case Integer.parse(str) do
|
|
{qty, _} when qty > 0 -> qty
|
|
_ -> 1
|
|
end
|
|
end
|
|
|
|
defp parse_quantity(_), do: 1
|
|
|
|
# Allows 0 and negative values so Cart.update_quantity can remove items
|
|
defp parse_update_quantity(str) when is_binary(str) do
|
|
case Integer.parse(str) do
|
|
{qty, _} -> qty
|
|
:error -> 1
|
|
end
|
|
end
|
|
|
|
defp parse_update_quantity(_), do: 1
|
|
end
|