defmodule BerrypodWeb.Plugs.Analytics do @moduledoc """ Plug that records a pageview event on every shop HTTP request. This is Layer 1 of the progressive analytics pipeline — it fires on every GET request regardless of whether JS is enabled. Computes a privacy-friendly visitor hash, parses the user agent, extracts referrer and UTM params, and buffers the event for batch writing to SQLite. Also stores analytics data in the session so the LiveView hook (Layer 2) can read it for subsequent SPA navigations. """ import Plug.Conn alias Berrypod.Analytics alias Berrypod.Analytics.{Salt, UAParser, Referrer} def init(opts), do: opts def call(%{method: "GET"} = conn, _opts) do ua = get_req_header(conn, "user-agent") |> List.first("") {browser, os} = UAParser.parse(ua) # Skip bots if browser == "Bot" do conn else # Skip if the logged-in admin is browsing if admin?(conn) do prepare_session(conn, ua, browser, os) else record_and_prepare(conn, ua, browser, os) end end end def call(conn, _opts), do: conn defp record_and_prepare(conn, ua, browser, os) do visitor_hash = Salt.hash_visitor(conn.remote_ip, ua) {referrer, referrer_source} = extract_referrer(conn) utm = extract_utms(conn) country_code = get_session(conn, "country_code") screen_size = get_session(conn, "analytics_screen_size") Analytics.track_pageview(%{ pathname: conn.request_path, visitor_hash: visitor_hash, referrer: referrer, referrer_source: referrer_source, utm_source: utm.source, utm_medium: utm.medium, utm_campaign: utm.campaign, country_code: country_code, screen_size: screen_size, browser: browser, os: os }) conn |> put_session("analytics_visitor_hash", visitor_hash) |> put_session("analytics_browser", browser) |> put_session("analytics_os", os) |> put_session("analytics_referrer", referrer) |> put_session("analytics_referrer_source", referrer_source) |> put_session("analytics_utm_source", utm.source) |> put_session("analytics_utm_medium", utm.medium) |> put_session("analytics_utm_campaign", utm.campaign) end # Store session data for the hook even when we skip recording for admins defp prepare_session(conn, ua, browser, os) do visitor_hash = Salt.hash_visitor(conn.remote_ip, ua) conn |> put_session("analytics_visitor_hash", visitor_hash) |> put_session("analytics_browser", browser) |> put_session("analytics_os", os) end defp admin?(conn) do case conn.assigns[:current_scope] do %{user: %{}} -> true _ -> false end end defp extract_referrer(conn) do referrer_url = get_req_header(conn, "referer") |> List.first() {referrer, source} = Referrer.parse(referrer_url) # Don't count self-referrals host = conn.host if referrer && referrer == host do {nil, nil} else {referrer, source} end end defp extract_utms(conn) do params = conn.query_params || %{} %{ source: Map.get(params, "utm_source"), medium: Map.get(params, "utm_medium"), campaign: Map.get(params, "utm_campaign") } end end