defmodule BerrypodWeb.Plugs.Analytics do @moduledoc """ Plug that records an initial pageview and prepares analytics session data. Fires on every GET request regardless of JS — computes a privacy-friendly visitor hash, parses the user agent, extracts referrer and UTM params. Records the pageview immediately (for no-JS support) and stores the data in the session for the LiveView hook to use on SPA navigations. The event is recorded with a known ID (plug_ref) stored in the session. When JS connects, the LiveView hook supersedes this event by ID and records its own with full data (screen_size). If JS never connects, the Plug's event flushes to the DB normally. """ 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 recording for logged-in admin — don't set analytics session # data either, so downstream guards (visitor_hash checks in LiveViews) # naturally filter out admin browsing for all event types if admin?(conn) do conn 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") plug_ref = Ecto.UUID.generate() Analytics.track_pageview(%{ id: plug_ref, 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: nil, 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) |> put_session("analytics_plug_ref", plug_ref) 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