diff --git a/lib/plausible/google/api.ex b/lib/plausible/google/api.ex index 14df65ae3..6a6f2f7d1 100644 --- a/lib/plausible/google/api.ex +++ b/lib/plausible/google/api.ex @@ -1,5 +1,6 @@ defmodule Plausible.Google.Api do @scope URI.encode_www_form("https://www.googleapis.com/auth/webmasters.readonly email") + @verified_permission_levels ["siteOwner", "siteFullUser", "siteRestrictedUser"] def authorize_url(site_id) do if Mix.env() == :test do @@ -14,32 +15,20 @@ defmodule Plausible.Google.Api do Jason.decode!(res.body) end - def fetch_site(domain, auth) do - auth = if Timex.before?(auth.expires, Timex.now() |> Timex.shift(seconds: 5)) do - refresh_token(auth) - else - auth - end - with_https = URI.encode_www_form("https://#{domain}") - - res = HTTPoison.get!("https://www.googleapis.com/webmasters/v3/sites/#{with_https}",["Content-Type": "application/json", "Authorization": "Bearer #{auth.access_token}"]) - + def fetch_verified_properties(auth) do + auth = refresh_if_needed(auth) + res = HTTPoison.get!("https://www.googleapis.com/webmasters/v3/sites",["Content-Type": "application/json", "Authorization": "Bearer #{auth.access_token}"]) Jason.decode!(res.body) + |> Map.get("siteEntry") + |> Enum.filter(fn site -> site["permissionLevel"] in @verified_permission_levels end) + |> Enum.map(fn site -> site["siteUrl"] end) end - def fetch_stats(site, auth, query) do - if Timex.before?(auth.expires, Timex.now() |> Timex.shift(seconds: 5)) do - auth = refresh_token(auth) - fetch_queries(site.domain, auth, query) - else - fetch_queries(site.domain, auth, query) - end - end + def fetch_stats(auth, query) do + auth = refresh_if_needed(auth) + property = URI.encode_www_form(auth.property) - defp fetch_queries(domain, auth, query) do - with_https = URI.encode_www_form("https://#{domain}") - - res = HTTPoison.post!("https://www.googleapis.com/webmasters/v3/sites/#{with_https}/searchAnalytics/query", Jason.encode!(%{ + res = HTTPoison.post!("https://www.googleapis.com/webmasters/v3/sites/#{property}/searchAnalytics/query", Jason.encode!(%{ startDate: Date.to_iso8601(query.date_range.first), endDate: Date.to_iso8601(query.date_range.last), dimensions: ["query"], @@ -66,6 +55,14 @@ defmodule Plausible.Google.Api do end end + defp refresh_if_needed(auth) do + if Timex.before?(auth.expires, Timex.now() |> Timex.shift(seconds: 30)) do + refresh_token(auth) + else + auth + end + end + defp refresh_token(auth) do res = HTTPoison.post!("https://www.googleapis.com/oauth2/v4/token", "client_id=#{client_id()}&client_secret=#{client_secret()}&refresh_token=#{auth.refresh_token}&grant_type=refresh_token&redirect_uri=#{redirect_uri()}", ["Content-Type": "application/x-www-form-urlencoded"]) body = Jason.decode!(res.body) diff --git a/lib/plausible/site/google_auth.ex b/lib/plausible/site/google_auth.ex index c391c7173..d19b9d5a9 100644 --- a/lib/plausible/site/google_auth.ex +++ b/lib/plausible/site/google_auth.ex @@ -4,6 +4,7 @@ defmodule Plausible.Site.GoogleAuth do schema "google_auth" do field :email, :string + field :property, :string field :refresh_token, :string field :access_token, :string field :expires, :naive_datetime @@ -20,4 +21,9 @@ defmodule Plausible.Site.GoogleAuth do |> validate_required([:refresh_token, :access_token, :expires, :email, :user_id, :site_id]) |> unique_constraint(:site) end + + def set_property(auth, attrs \\ %{}) do + auth + |> cast(attrs, [:property]) + end end diff --git a/lib/plausible_web/controllers/site_controller.ex b/lib/plausible_web/controllers/site_controller.ex index 04eb6feae..282802234 100644 --- a/lib/plausible_web/controllers/site_controller.ex +++ b/lib/plausible_web/controllers/site_controller.ex @@ -37,9 +37,8 @@ defmodule PlausibleWeb.SiteController do site = Sites.get_for_user!(conn.assigns[:current_user].id, website) |> Repo.preload(:google_auth) - google_search_console_verified = if site.google_auth do - google_site = Plausible.Google.Api.fetch_site(site.domain, site.google_auth) - !google_site["error"] + search_console_domains = if site.google_auth do + Plausible.Google.Api.fetch_verified_properties(site.google_auth) end weekly_report = Repo.get_by(Plausible.Site.WeeklyReport, site_id: site.id) @@ -53,11 +52,23 @@ defmodule PlausibleWeb.SiteController do site: site, weekly_report_changeset: weekly_report_changeset, monthly_report_changeset: monthly_report_changeset, - google_search_console_verified: google_search_console_verified, + search_console_domains: search_console_domains, changeset: Plausible.Site.changeset(site, %{}) ) end + def update_google_auth(conn, %{"website" => website, "google_auth" => attrs}) do + site = Sites.get_for_user!(conn.assigns[:current_user].id, website) + |> Repo.preload(:google_auth) + + Plausible.Site.GoogleAuth.set_property(site.google_auth, attrs) + |> Repo.update! + + conn + |> put_flash(:success, "Google integration saved succesfully") + |> redirect(to: "/#{site.domain}/settings") + end + def update_settings(conn, %{"website" => website, "site" => site_params}) do site = Sites.get_for_user!(conn.assigns[:current_user].id, website) changeset = site |> Plausible.Site.changeset(site_params) @@ -179,6 +190,20 @@ defmodule PlausibleWeb.SiteController do |> redirect(to: "/#{site.domain}/settings") end + def google_settings(conn, %{"website" => website}) do + site = Sites.get_for_user!(conn.assigns[:current_user].id, website) + |> Repo.preload(:google_auth) + + verified_domains = Plausible.Google.Api.fetch_verified_properties(site.google_auth) + + render(conn, + "google_settings.html", + site: site, + verified_domains: verified_domains, + layout: {PlausibleWeb.LayoutView, "focus.html"} + ) + end + defp insert_site(user_id, params) do site_changeset = Plausible.Site.changeset(%Plausible.Site{}, params) diff --git a/lib/plausible_web/controllers/stats_controller.ex b/lib/plausible_web/controllers/stats_controller.ex index e6ac5b163..139e398cc 100644 --- a/lib/plausible_web/controllers/stats_controller.ex +++ b/lib/plausible_web/controllers/stats_controller.ex @@ -213,8 +213,8 @@ defmodule PlausibleWeb.StatsController do {conn, params} = fetch_period(conn, site) query = Stats.Query.from(site.timezone, params) total_visitors = Stats.visitors_from_referrer(site, query, "Google") - search_terms = if site.google_auth do - Plausible.Google.Api.fetch_stats(site, site.google_auth, query) + search_terms = if site.google_auth && site.google_auth.property do + Plausible.Google.Api.fetch_stats(site.google_auth, query) end render(conn, "google_referrer.html", diff --git a/lib/plausible_web/router.ex b/lib/plausible_web/router.ex index 9195f029a..40ed821f4 100644 --- a/lib/plausible_web/router.ex +++ b/lib/plausible_web/router.ex @@ -94,6 +94,7 @@ defmodule PlausibleWeb.Router do get "/:website/snippet", SiteController, :add_snippet get "/:website/settings", SiteController, :settings put "/:website/settings", SiteController, :update_settings + put "/:website/settings/google", SiteController, :update_google_auth delete "/:website", SiteController, :delete_site get "/stats/:domain/referrers", StatsController, :referrers_preview diff --git a/lib/plausible_web/templates/layout/app.html.eex b/lib/plausible_web/templates/layout/app.html.eex index 915a483d4..e39a8321c 100644 --- a/lib/plausible_web/templates/layout/app.html.eex +++ b/lib/plausible_web/templates/layout/app.html.eex @@ -6,7 +6,7 @@ "> - <%= assigns[:title] || "Plausible · Simple web analytics" %> + <%= assigns[:title] || "Plausible · Simple, open-source web analytics" %> "/> <%= render("_tracking.html", assigns) %> diff --git a/lib/plausible_web/templates/site/google_settings.html.eex b/lib/plausible_web/templates/site/google_settings.html.eex new file mode 100644 index 000000000..b326e7936 --- /dev/null +++ b/lib/plausible_web/templates/site/google_settings.html.eex @@ -0,0 +1,16 @@ +<%= form_for @conn, "/google", [class: "max-w-sm w-full mx-auto bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %> +

Connect to property

+

Select the Google Search Console property you would like to pull keyword data from

+ +
+ <%= label f, :domain, class: "block text-grey-darker text-sm font-bold mb-2" %> +
+ <%= select f, :domain, @verified_domains, class: "block appearance-none w-full bg-grey-lighter text-grey-darker cursor-pointer hover:border-grey p-2 pr-8 rounded leading-normal focus:outline-none" %> +
+ +
+
+
+ + <%= submit "Enable Integration →", class: "button mt-4 w-full" %> +<% end %> diff --git a/lib/plausible_web/templates/site/settings.html.eex b/lib/plausible_web/templates/site/settings.html.eex index 69d63a3ad..af544153e 100644 --- a/lib/plausible_web/templates/site/settings.html.eex +++ b/lib/plausible_web/templates/site/settings.html.eex @@ -62,34 +62,35 @@ <%= if @site.google_auth do %>
- - - Connected Google account: <%= @site.google_auth.email %> - <%= if @google_search_console_verified do %> -
- - - - Verified access to site https://<%= @site.domain %> -
- <%= link("View google stats", to: "/#{@site.domain}/referrers/Google", class: "text-indigo") %> -
+ + <%= if @site.google_auth.property && !(@site.google_auth.property in @search_console_domains) do %> +

+ NB: Your Google account does not have access to your currently configured property, <%= @site.google_auth.property %>. Please select a verified property from the list below. +

<% else %> -
- - - - No access to site https://<%= @site.domain %> -
- <%= link("Verify your site on Google Search Console to continue", to: "https://support.google.com/webmasters/answer/34592", class: "text-indigo") %> +

+ Select the Google Search Console property you would like to pull keyword data from. If you don't see your domain, <%= link("set it up and verify", to: "https://docs.plausible.io/google-search-console-integration#1-add-your-site-on-the-search-console", class: "text-indigo") %> on Search Console first. +

+ <% end %> + + <%= form_for Plausible.Site.GoogleAuth.changeset(@site.google_auth), "/#{@site.domain}/settings/google", [class: "max-w-xs"], fn f -> %> +
+
+ <%= select f, :property, @search_console_domains, prompt: "(Choose property)", class: "block appearance-none w-full bg-grey-lighter text-grey-darker cursor-pointer hover:border-grey p-2 pr-8 rounded leading-normal focus:outline-none" %> +
+ +
+
+ + <%= submit "Save", class: "button" %> <% end %> <% else %> <%= button("Continue with Google", to: Plausible.Google.Api.authorize_url(@site.id), class: "button mt-4") %>
- NB: You also need to set up your site on <%= link("Google Search Console", to: "https://search.google.com/search-console/about") %> for the integration to work. + NB: You also need to set up your site on <%= link("Google Search Console", to: "https://search.google.com/search-console/about") %> for the integration to work. <%= link("Read the docs", to: "https://docs.plausible.io/google-search-console-integration", class: "text-indigo") %>
<% end %>
diff --git a/priv/repo/migrations/20191010031425_add_property_to_google_auth.exs b/priv/repo/migrations/20191010031425_add_property_to_google_auth.exs new file mode 100644 index 000000000..9601d1cce --- /dev/null +++ b/priv/repo/migrations/20191010031425_add_property_to_google_auth.exs @@ -0,0 +1,19 @@ +defmodule Plausible.Repo.Migrations.AddPropertyToGoogleAuth do + use Ecto.Migration + use Plausible.Repo + + def change do + alter table(:google_auth) do + add :property, :text + end + + flush() + + for auth <- Repo.all(Plausible.Site.GoogleAuth) do + auth = Repo.preload(auth, :site) + property = "https://#{auth.site.domain}" + Plausible.Site.GoogleAuth.set_property(auth, %{property: property}) + |> Repo.update! + end + end +end