defmodule Plausible.Google.HTTP do @moduledoc """ HTTP client implementation for Google APIs. """ require Logger alias Plausible.HTTPClient def list_sites(access_token) do url = "#{api_url()}/webmasters/v3/sites" headers = [{"Content-Type", "application/json"}, {"Authorization", "Bearer #{access_token}"}] case HTTPClient.impl().get(url, headers) do {:ok, %{body: body}} -> {:ok, body} {:error, %{reason: %{status: s}}} when s in [401, 403] -> {:error, "google_auth_error"} {:error, %{reason: %{body: %{"error" => error}}}} -> {:error, error} {:error, reason} -> Logger.error("Google Analytics: failed to list sites: #{inspect(reason)}") {:error, "failed_to_list_sites"} end end def fetch_access_token!(code) do url = "#{api_url()}/oauth2/v4/token" headers = [{"Content-Type", "application/x-www-form-urlencoded"}] params = %{ client_id: client_id(), client_secret: client_secret(), code: code, grant_type: :authorization_code, redirect_uri: redirect_uri() } {:ok, response} = HTTPClient.impl().post(url, headers, params) response.body end def list_stats(access_token, property, date_range, pagination, search_console_filters) do {limit, page} = pagination params = %{ startDate: Date.to_iso8601(date_range.first), endDate: Date.to_iso8601(date_range.last), dimensions: ["query"], rowLimit: limit, startRow: page * limit, dimensionFilterGroups: search_console_filters } url = "#{api_url()}/webmasters/v3/sites/#{URI.encode_www_form(property)}/searchAnalytics/query" headers = [{"Authorization", "Bearer #{access_token}"}] case HTTPClient.impl().post(url, headers, params) do {:ok, %Finch.Response{body: body, status: 200}} -> {:ok, body} {:error, %{reason: %Finch.Response{body: _body, status: status}}} when status in [401, 403] -> {:error, "google_auth_error"} {:error, %{reason: %{body: %{"error" => error}}}} -> {:error, error} {:error, reason} -> Logger.error("Google Search Console: failed to list stats: #{inspect(reason)}") {:error, "failed_to_list_stats"} end end def refresh_auth_token(refresh_token) do url = "#{api_url()}/oauth2/v4/token" headers = [{"content-type", "application/x-www-form-urlencoded"}] params = %{ client_id: client_id(), client_secret: client_secret(), refresh_token: refresh_token, grant_type: :refresh_token, redirect_uri: redirect_uri() } case HTTPClient.impl().post(url, headers, params) do {:ok, %Finch.Response{body: body, status: 200}} -> {:ok, body} {:error, %{reason: %Finch.Response{body: %{"error" => error}, status: _non_http_200}}} -> {:error, error} {:error, %{reason: _} = e} -> Sentry.capture_message("Error fetching Google queries", extra: %{error: inspect(e)}) {:error, :unknown_error} end end defp config, do: Application.get_env(:plausible, :google) defp client_id, do: Keyword.fetch!(config(), :client_id) defp client_secret, do: Keyword.fetch!(config(), :client_secret) defp api_url, do: Keyword.fetch!(config(), :api_url) defp redirect_uri, do: PlausibleWeb.Endpoint.url() <> "/auth/google/callback" end