defimpl FunWithFlags.Actor, for: BitString do def id(str) do str end end defmodule PlausibleWeb.Api.ExternalController do use PlausibleWeb, :controller require Logger alias Plausible.Ingestion def event(conn, _params) do with {:ok, request} <- Ingestion.Request.build(conn), _ <- Sentry.Context.set_extra_context(%{request: request}) do case Ingestion.Event.build_and_buffer(request) do {:ok, %{dropped: [], buffered: _buffered}} -> conn |> put_status(202) |> text("ok") {:ok, %{dropped: dropped, buffered: _}} -> first_invalid_changeset = find_first_invalid_changeset(dropped) if first_invalid_changeset do conn |> put_resp_header("x-plausible-dropped", "#{Enum.count(dropped)}") |> put_status(400) |> json(%{errors: Plausible.ChangesetHelpers.traverse_errors(first_invalid_changeset)}) else conn |> put_resp_header("x-plausible-dropped", "#{Enum.count(dropped)}") |> put_status(202) |> text("ok") end end else {:error, %Ecto.Changeset{} = changeset} -> conn |> put_status(400) |> json(%{errors: Plausible.ChangesetHelpers.traverse_errors(changeset)}) end end def error(conn, _params) do Sentry.capture_message("JS snippet error") send_resp(conn, 200, "") end def health(conn, _params) do postgres_health = case Ecto.Adapters.SQL.query(Plausible.Repo, "SELECT 1", []) do {:ok, _} -> "ok" e -> "error: #{inspect(e)}" end clickhouse_health = case Ecto.Adapters.SQL.query(Plausible.ClickhouseRepo, "SELECT 1", []) do {:ok, _} -> "ok" e -> "error: #{inspect(e)}" end cache_health = if postgres_health == "ok" and Plausible.Site.Cache.ready?() and Plausible.Shield.IPRuleCache.ready?() do "ok" end status = case {postgres_health, clickhouse_health, cache_health} do {"ok", "ok", "ok"} -> 200 _ -> 500 end put_status(conn, status) |> json(%{ postgres: postgres_health, clickhouse: clickhouse_health, sites_cache: cache_health }) end def info(conn, _params) do build = :plausible |> Application.get_env(:runtime_metadata) |> Keyword.take([:version, :commit, :created, :tags]) |> Map.new() geo_database = Plausible.Geo.database_type() || "(not configured)" json(conn, %{ geo_database: geo_database, build: build }) end defp find_first_invalid_changeset(dropped) do Enum.find_value(dropped, nil, fn dropped_event -> case dropped_event.drop_reason do {:error, %Ecto.Changeset{} = changeset} -> changeset _ -> false end end) end end