diff --git a/extra/lib/plausible_web/controllers/api/external_sites_controller.ex b/extra/lib/plausible_web/controllers/api/external_sites_controller.ex index 1ea08a3e08b4..4e3a0bb930c2 100644 --- a/extra/lib/plausible_web/controllers/api/external_sites_controller.ex +++ b/extra/lib/plausible_web/controllers/api/external_sites_controller.ex @@ -52,6 +52,30 @@ defmodule PlausibleWeb.Api.ExternalSitesController do end end + def teams_index(conn, params) do + user = conn.assigns.current_user + + page = + user + |> Teams.Users.teams_query(order_by: :id_desc) + |> paginate(params, @pagination_opts) + + json(conn, %{ + teams: + Enum.map(page.entries, fn team -> + api_available? = + Plausible.Billing.Feature.StatsAPI.check_availability(team) == :ok + + %{ + id: team.identifier, + name: Teams.name(team), + api_available: api_available? + } + end), + meta: pagination_meta(page.metadata) + }) + end + def goals_index(conn, params) do user = conn.assigns.current_user diff --git a/lib/plausible/sites.ex b/lib/plausible/sites.ex index a3c801bab5aa..0d09a458e6f5 100644 --- a/lib/plausible/sites.ex +++ b/lib/plausible/sites.ex @@ -231,7 +231,7 @@ defmodule Plausible.Sites do where( query, [team_memberships: tm, guest_memberships: gm, site: s], - (tm.role != :guest and tm.team_id == ^team.id) or gm.site_id == s.id + tm.role != :guest and tm.team_id == ^team.id ) else where( diff --git a/lib/plausible/teams/users.ex b/lib/plausible/teams/users.ex index 60f01886f9bb..b4601e53518e 100644 --- a/lib/plausible/teams/users.ex +++ b/lib/plausible/teams/users.ex @@ -21,18 +21,38 @@ defmodule Plausible.Teams.Users do end def teams(user) do - from( - tm in Teams.Membership, - inner_join: t in assoc(tm, :team), - where: tm.user_id == ^user.id, - where: tm.role != :guest, - select: t, - order_by: [t.name, t.id] - ) + user + |> teams_query(order_by: :name) |> Repo.all() |> Repo.preload(:owners) end + def teams_query(user, opts \\ []) do + order_by = Keyword.get(opts, :order_by, :name) + + query = + from( + tm in Teams.Membership, + as: :team_membership, + inner_join: t in assoc(tm, :team), + as: :team, + where: tm.user_id == ^user.id, + where: tm.role != :guest, + select: t + ) + + case order_by do + :name -> + order_by(query, [team: t], [t.name, t.id]) + + :id_desc -> + order_by(query, [team: t], desc: t.id) + + _ -> + query + end + end + def team_member?(user, opts \\ []) do excluded_team_ids = Keyword.get(opts, :except, []) diff --git a/lib/plausible_web/router.ex b/lib/plausible_web/router.ex index 10c5048cc075..ebde84bfcdc5 100644 --- a/lib/plausible_web/router.ex +++ b/lib/plausible_web/router.ex @@ -266,6 +266,7 @@ defmodule PlausibleWeb.Router do pipe_through PlausibleWeb.Plugs.AuthorizePublicAPI get "/", ExternalSitesController, :index + get "/teams", ExternalSitesController, :teams_index get "/goals", ExternalSitesController, :goals_index get "/guests", ExternalSitesController, :guests_index get "/:site_id", ExternalSitesController, :get_site diff --git a/lib/plausible_web/templates/settings/team_general.html.heex b/lib/plausible_web/templates/settings/team_general.html.heex index 7315c75cf59a..e8585895a09d 100644 --- a/lib/plausible_web/templates/settings/team_general.html.heex +++ b/lib/plausible_web/templates/settings/team_general.html.heex @@ -12,14 +12,6 @@ for={@team_name_changeset} method="post" > -
- <.input_with_clipboard - name="team-identifier" - id="team-identifier" - label="Team Identifier" - value={@current_team.identifier} - /> -
<.input readonly={@current_team_role not in [:owner, :admin]} type="text" diff --git a/test/plausible_web/controllers/api/external_sites_controller_test.exs b/test/plausible_web/controllers/api/external_sites_controller_test.exs index 4253930557fe..5cc2d3e1f961 100644 --- a/test/plausible_web/controllers/api/external_sites_controller_test.exs +++ b/test/plausible_web/controllers/api/external_sites_controller_test.exs @@ -14,6 +14,68 @@ defmodule PlausibleWeb.Api.ExternalSitesControllerTest do {:ok, api_key: api_key, conn: conn} end + describe "GET /api/v1/sites/teams" do + test "shows empty list when user is not a member of any team", %{conn: conn} do + conn = get(conn, "/api/v1/sites/teams") + + assert json_response(conn, 200) == %{ + "teams" => [], + "meta" => %{ + "before" => nil, + "after" => nil, + "limit" => 100 + } + } + end + + test "shows list of teams user is a member of with api availability reflecting team state", + %{conn: conn, user: user} do + user |> subscribe_to_growth_plan() + + personal_team = team_of(user) + + owner1 = + new_user( + trial_expiry_date: Date.add(Date.utc_today(), -1), + team: [name: "Team Without Stats API"] + ) + |> subscribe_to_enterprise_plan(features: []) + + team_without_stats = owner1 |> team_of() |> Plausible.Teams.complete_setup() + add_member(team_without_stats, user: user, role: :editor) + owner2 = new_user(team: [name: "Team With Stats API"]) + team_with_stats = owner2 |> team_of() |> Plausible.Teams.complete_setup() + add_member(team_with_stats, user: user, role: :owner) + + conn = get(conn, "/api/v1/sites/teams") + + assert json_response(conn, 200) == %{ + "teams" => [ + %{ + "id" => team_with_stats.identifier, + "name" => "Team With Stats API", + "api_available" => true + }, + %{ + "id" => team_without_stats.identifier, + "name" => "Team Without Stats API", + "api_available" => false + }, + %{ + "id" => personal_team.identifier, + "name" => "My Personal Sites", + "api_available" => false + } + ], + "meta" => %{ + "before" => nil, + "after" => nil, + "limit" => 100 + } + } + end + end + describe "POST /api/v1/sites" do test "can create a site", %{conn: conn} do conn = @@ -28,6 +90,45 @@ defmodule PlausibleWeb.Api.ExternalSitesControllerTest do } end + test "can't create site in a team where not permitted to", %{conn: conn, user: user} do + owner = new_user() |> subscribe_to_growth_plan() + team = owner |> team_of() |> Plausible.Teams.complete_setup() + add_member(team, user: user, role: :viewer) + + conn = + post(conn, "/api/v1/sites", %{ + "team_id" => team.identifier, + "domain" => "some-site.domain", + "timezone" => "Europe/Tallinn" + }) + + assert json_response(conn, 403) == %{ + "error" => "You can't add sites to the selected team." + } + end + + test "can create a site under a specific team if permitted", %{conn: conn, user: user} do + _site = new_site(owner: user) + + owner = new_user() |> subscribe_to_growth_plan() + team = owner |> team_of() |> Plausible.Teams.complete_setup() + add_member(team, user: user, role: :owner) + + conn = + post(conn, "/api/v1/sites", %{ + "team_id" => team.identifier, + "domain" => "some-site.domain", + "timezone" => "Europe/Tallinn" + }) + + assert json_response(conn, 200) == %{ + "domain" => "some-site.domain", + "timezone" => "Europe/Tallinn" + } + + assert Repo.get_by(Plausible.Site, domain: "some-site.domain").team_id == team.id + end + test "timezone is validated", %{conn: conn} do conn = post(conn, "/api/v1/sites", %{ @@ -580,8 +681,7 @@ defmodule PlausibleWeb.Api.ExternalSitesControllerTest do assert_matches %{ "sites" => [ - %{"domain" => ^other_team_site.domain}, - %{"domain" => ^other_site.domain} + %{"domain" => ^other_team_site.domain} ] } = json_response(conn, 200) end diff --git a/test/plausible_web/controllers/auth_controller_test.exs b/test/plausible_web/controllers/auth_controller_test.exs index 8e67e1969b00..dd4dc6d95ad2 100644 --- a/test/plausible_web/controllers/auth_controller_test.exs +++ b/test/plausible_web/controllers/auth_controller_test.exs @@ -707,7 +707,8 @@ defmodule PlausibleWeb.AuthControllerTest do } do another_owner = new_user() another_site = new_site(owner: another_owner) - add_member(another_site.team, user: user, role: :admin) + another_team = another_owner |> team_of() |> Plausible.Teams.complete_setup() + add_member(another_team, user: user, role: :admin) segment = insert(:segment, @@ -728,7 +729,8 @@ defmodule PlausibleWeb.AuthControllerTest do } do another_owner = new_user() another_site = new_site(owner: another_owner) - add_member(another_site.team, user: user, role: :admin) + another_team = another_owner |> team_of() |> Plausible.Teams.complete_setup() + add_member(another_team, user: user, role: :admin) segment = insert(:segment, @@ -749,7 +751,7 @@ defmodule PlausibleWeb.AuthControllerTest do } do another_owner = new_user() another_site = new_site(owner: another_owner) - team = team_of(another_owner) + team = another_owner |> team_of() |> Plausible.Teams.complete_setup() add_member(another_site.team, user: user, role: :owner) delete(conn, "/me") @@ -766,7 +768,7 @@ defmodule PlausibleWeb.AuthControllerTest do personal_team = team_of(user) another_owner = new_user() _another_site = new_site(owner: another_owner) - another_team = team_of(another_owner) + another_team = another_owner |> team_of() |> Plausible.Teams.complete_setup() add_member(another_team, user: user, role: :owner) delete(conn, "/me") diff --git a/test/plausible_web/controllers/settings_controller_test.exs b/test/plausible_web/controllers/settings_controller_test.exs index dd51e4e839aa..6aac420b09c4 100644 --- a/test/plausible_web/controllers/settings_controller_test.exs +++ b/test/plausible_web/controllers/settings_controller_test.exs @@ -1182,7 +1182,6 @@ defmodule PlausibleWeb.SettingsControllerTest do assert html =~ "Team Information" assert html =~ "Change the name of your team" assert text_of_attr(html, "input#team_name", "value") == team.name - assert text_of_attr(html, "input#team-identifier", "value") == team.identifier end test "POST /settings/team/general/name", %{conn: conn, user: user} do