diff --git a/lib/plausible/sites.ex b/lib/plausible/sites.ex index 33dc60e50bca..d6be09a274f5 100644 --- a/lib/plausible/sites.ex +++ b/lib/plausible/sites.ex @@ -6,7 +6,6 @@ defmodule Plausible.Sites do import Ecto.Query alias Plausible.Auth - alias Plausible.Billing.Quota alias Plausible.Repo alias Plausible.Site alias Plausible.Site.SharedLink @@ -84,7 +83,7 @@ defmodule Plausible.Sites do end def create(user, params) do - with :ok <- Quota.ensure_can_add_new_site(user) do + with :ok <- Plausible.Teams.Adapter.Read.Billing.ensure_can_add_new_site(user) do Ecto.Multi.new() |> Ecto.Multi.put(:site_changeset, Site.new(params)) |> Ecto.Multi.run(:create_team, fn _repo, _context -> diff --git a/lib/plausible/teams.ex b/lib/plausible/teams.ex index 438a0a697c7e..f34fec53566c 100644 --- a/lib/plausible/teams.ex +++ b/lib/plausible/teams.ex @@ -138,7 +138,7 @@ defmodule Plausible.Teams do defp last_subscription_query() do from(subscription in Plausible.Billing.Subscription, - order_by: [desc: subscription.inserted_at], + order_by: [desc: subscription.inserted_at, desc: subscription.id], limit: 1 ) end diff --git a/lib/plausible/teams/adapter.ex b/lib/plausible/teams/adapter.ex new file mode 100644 index 000000000000..63333f5e71b7 --- /dev/null +++ b/lib/plausible/teams/adapter.ex @@ -0,0 +1,38 @@ +defmodule Plausible.Teams.Adapter do + @moduledoc """ + Commonly used teams-transition functions + """ + alias Plausible.Teams + + defmacro __using__(_) do + quote do + alias Plausible.Teams + import Teams.Adapter + end + end + + def team_or_user(user) do + switch( + user, + team_fn: &Function.identity/1, + user_fn: &Function.identity/1 + ) + end + + def switch(user, opts \\ []) do + team_fn = Keyword.fetch!(opts, :team_fn) + user_fn = Keyword.fetch!(opts, :user_fn) + + if Teams.read_team_schemas?(user) do + team = + case Teams.get_by_owner(user) do + {:ok, team} -> team + {:error, _} -> nil + end + + team_fn.(team) + else + user_fn.(user) + end + end +end diff --git a/lib/plausible/teams/adapter/read/billing.ex b/lib/plausible/teams/adapter/read/billing.ex index 82d238a41459..6a58bd842b53 100644 --- a/lib/plausible/teams/adapter/read/billing.ex +++ b/lib/plausible/teams/adapter/read/billing.ex @@ -2,19 +2,36 @@ defmodule Plausible.Teams.Adapter.Read.Billing do @moduledoc """ Transition adapter for new schema reads """ - alias Plausible.Teams + use Plausible.Teams.Adapter def check_needs_to_upgrade(user) do - if Teams.read_team_schemas?(user) do - team = - case Teams.get_by_owner(user) do - {:ok, team} -> team - {:error, _} -> nil - end + switch( + user, + team_fn: &Teams.Billing.check_needs_to_upgrade/1, + user_fn: &Plausible.Billing.check_needs_to_upgrade/1 + ) + end + + def site_limit(user) do + switch( + user, + team_fn: &Teams.Billing.site_limit/1, + user_fn: &Plausible.Billing.Quota.Limits.site_limit/1 + ) + end + + def ensure_can_add_new_site(user) do + switch( + user, + team_fn: &Teams.Billing.ensure_can_add_new_site/1, + user_fn: &Plausible.Billing.Quota.ensure_can_add_new_site/1 + ) + end - Teams.Billing.check_needs_to_upgrade(team) - else - Plausible.Billing.check_needs_to_upgrade(user) - end + def site_usage(user) do + switch(user, + team_fn: &Teams.Billing.site_usage/1, + user_fn: &Plausible.Billing.Quota.Usage.site_usage/1 + ) end end diff --git a/lib/plausible/teams/adapter/read/ownership.ex b/lib/plausible/teams/adapter/read/ownership.ex index 4a04ace2fc1c..35f691e31d53 100644 --- a/lib/plausible/teams/adapter/read/ownership.ex +++ b/lib/plausible/teams/adapter/read/ownership.ex @@ -3,59 +3,47 @@ defmodule Plausible.Teams.Adapter.Read.Ownership do Transition adapter for new schema reads """ use Plausible + use Plausible.Teams.Adapter alias Plausible.Site alias Plausible.Auth - alias Plausible.Teams alias Plausible.Site.Memberships.Invitations def ensure_can_take_ownership(site, user) do - if Teams.read_team_schemas?(user) do - team = - case Teams.get_by_owner(user) do - {:ok, team} -> team - {:error, _} -> nil - end - - Teams.Invitations.ensure_can_take_ownership(site, team) - else - Invitations.ensure_can_take_ownership(site, user) - end + switch( + user, + team_fn: &Teams.Invitations.ensure_can_take_ownership(site, &1), + user_fn: &Invitations.ensure_can_take_ownership(site, &1) + ) end def has_sites?(user) do - if Teams.read_team_schemas?(user) do - Teams.Users.has_sites?(user, include_pending?: true) - else - Site.Memberships.any_or_pending?(user) - end + switch( + user, + team_fn: fn _ -> Teams.Users.has_sites?(user, include_pending?: true) end, + user_fn: &Site.Memberships.any_or_pending?/1 + ) end def owns_sites?(user, sites) do - if Teams.read_team_schemas?(user) do - Teams.Users.owns_sites?(user, include_pending?: true) - else - Enum.any?(sites.entries, fn site -> - length(site.invitations) > 0 && List.first(site.invitations).role == :owner - end) || - Auth.user_owns_sites?(user) - end + switch( + user, + team_fn: fn _ -> Teams.Users.owns_sites?(user, include_pending?: true) end, + user_fn: fn user -> + Enum.any?(sites.entries, fn site -> + length(site.invitations) > 0 && List.first(site.invitations).role == :owner + end) || + Auth.user_owns_sites?(user) + end + ) end on_ee do def check_feature_access(site, new_owner) do - user_or_team = - if Teams.read_team_schemas?(new_owner) do - case Teams.get_by_owner(new_owner) do - {:ok, team} -> team - {:error, _} -> nil - end - else - new_owner - end + team_or_user = team_or_user(new_owner) missing_features = Plausible.Billing.Quota.Usage.features_usage(nil, [site.id]) - |> Enum.filter(&(&1.check_availability(user_or_team) != :ok)) + |> Enum.filter(&(&1.check_availability(team_or_user) != :ok)) if missing_features == [] do :ok diff --git a/lib/plausible/teams/adapter/read/sites.ex b/lib/plausible/teams/adapter/read/sites.ex index 76ec43022a30..b38e9eeb4208 100644 --- a/lib/plausible/teams/adapter/read/sites.ex +++ b/lib/plausible/teams/adapter/read/sites.ex @@ -5,30 +5,29 @@ defmodule Plausible.Teams.Adapter.Read.Sites do import Ecto.Query - alias Plausible.Auth alias Plausible.Repo alias Plausible.Site - alias Plausible.Teams + use Plausible.Teams.Adapter def list(user, pagination_params, opts \\ []) do - if Plausible.Teams.read_team_schemas?(user) do - Plausible.Teams.Sites.list(user, pagination_params, opts) - else - old_list(user, pagination_params, opts) - end + switch( + user, + team_fn: fn _ -> Plausible.Teams.Sites.list(user, pagination_params, opts) end, + user_fn: fn _ -> old_list(user, pagination_params, opts) end + ) end def list_with_invitations(user, pagination_params, opts \\ []) do - if Plausible.Teams.read_team_schemas?(user) do - Plausible.Teams.Sites.list_with_invitations(user, pagination_params, opts) - else - old_list_with_invitations(user, pagination_params, opts) - end + switch( + user, + team_fn: fn _ -> + Plausible.Teams.Sites.list_with_invitations(user, pagination_params, opts) + end, + user_fn: fn _ -> old_list_with_invitations(user, pagination_params, opts) end + ) end - @type list_opt() :: {:filter_by_domain, String.t()} - @spec old_list(Auth.User.t(), map(), [list_opt()]) :: Scrivener.Page.t() - def old_list(user, pagination_params, opts \\ []) do + defp old_list(user, pagination_params, opts) do domain_filter = Keyword.get(opts, :filter_by_domain) from(s in Site, @@ -60,8 +59,7 @@ defmodule Plausible.Teams.Adapter.Read.Sites do |> Repo.paginate(pagination_params) end - @spec old_list_with_invitations(Auth.User.t(), map(), [list_opt()]) :: Scrivener.Page.t() - def old_list_with_invitations(user, pagination_params, opts \\ []) do + defp old_list_with_invitations(user, pagination_params, opts) do domain_filter = Keyword.get(opts, :filter_by_domain) result = diff --git a/lib/plausible/teams/billing.ex b/lib/plausible/teams/billing.ex index d9d2fbdec405..3db3f8dcb4c7 100644 --- a/lib/plausible/teams/billing.ex +++ b/lib/plausible/teams/billing.ex @@ -42,6 +42,10 @@ defmodule Plausible.Teams.Billing do end end + def ensure_can_add_new_site(nil) do + :ok + end + def ensure_can_add_new_site(team) do team = Teams.with_subscription(team) @@ -61,6 +65,10 @@ defmodule Plausible.Teams.Billing do end end + def site_limit(nil) do + @site_limit_for_trials + end + def site_limit(team) do if Timex.before?(team.inserted_at, @limit_sites_since) do :unlimited @@ -69,6 +77,8 @@ defmodule Plausible.Teams.Billing do end end + def site_usage(nil), do: 0 + def site_usage(team) do team |> Teams.owned_sites() @@ -76,7 +86,8 @@ defmodule Plausible.Teams.Billing do end defp get_site_limit_from_plan(team) do - team = Teams.with_subscription(team) + team = + Teams.with_subscription(team) case Plans.get_subscription_plan(team.subscription) do %{site_limit: site_limit} -> site_limit diff --git a/lib/plausible_web/controllers/site_controller.ex b/lib/plausible_web/controllers/site_controller.ex index b2f8620f8884..15a634d540c9 100644 --- a/lib/plausible_web/controllers/site_controller.ex +++ b/lib/plausible_web/controllers/site_controller.ex @@ -4,7 +4,6 @@ defmodule PlausibleWeb.SiteController do use Plausible alias Plausible.Sites - alias Plausible.Billing.Quota plug(PlausibleWeb.RequireAccountPlug) @@ -19,8 +18,9 @@ defmodule PlausibleWeb.SiteController do render(conn, "new.html", changeset: Plausible.Site.changeset(%Plausible.Site{}), - site_limit: Quota.Limits.site_limit(current_user), - site_limit_exceeded?: Quota.ensure_can_add_new_site(current_user) != :ok, + site_limit: Plausible.Teams.Adapter.Read.Billing.site_limit(current_user), + site_limit_exceeded?: + Plausible.Teams.Adapter.Read.Billing.ensure_can_add_new_site(current_user) != :ok, form_submit_url: "/sites?flow=#{flow}", flow: flow ) @@ -28,7 +28,7 @@ defmodule PlausibleWeb.SiteController do def create_site(conn, %{"site" => site_params}) do user = conn.assigns[:current_user] - first_site? = Quota.Usage.site_usage(user) == 0 + first_site? = Plausible.Teams.Adapter.Read.Billing.site_usage(user) == 0 flow = conn.params["flow"] case Sites.create(user, site_params) do @@ -60,7 +60,7 @@ defmodule PlausibleWeb.SiteController do render(conn, "new.html", changeset: changeset, first_site?: first_site?, - site_limit: Quota.Limits.site_limit(user), + site_limit: Plausible.Teams.Adapter.Read.Billing.site_limit(user), site_limit_exceeded?: false, flow: flow, form_submit_url: "/sites?flow=#{flow}" diff --git a/test/plausible/billing/quota_test.exs b/test/plausible/billing/quota_test.exs index 5b30719b9b6e..c4d87d6e5e73 100644 --- a/test/plausible/billing/quota_test.exs +++ b/test/plausible/billing/quota_test.exs @@ -4,6 +4,8 @@ defmodule Plausible.Billing.QuotaTest do alias Plausible.Billing.{Quota, Plans} alias Plausible.Billing.Feature.{Goals, Props, StatsAPI} + use Plausible.Teams.Test + on_ee do alias Plausible.Billing.Feature.Funnels alias Plausible.Billing.Feature.RevenueGoals @@ -22,56 +24,44 @@ defmodule Plausible.Billing.QuotaTest do @describetag :ee_only test "returns 50 when user is on an old plan" do - user_on_v1 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v1_plan_id)) - user_on_v2 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v2_plan_id)) - user_on_v3 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v3_plan_id)) + user_on_v1 = new_user() |> subscribe_to_plan(@v1_plan_id) + user_on_v2 = new_user() |> subscribe_to_plan(@v2_plan_id) + user_on_v3 = new_user() |> subscribe_to_plan(@v3_plan_id) - assert 50 == Quota.Limits.site_limit(user_on_v1) - assert 50 == Quota.Limits.site_limit(user_on_v2) - assert 50 == Quota.Limits.site_limit(user_on_v3) + assert 50 == Plausible.Teams.Adapter.Read.Billing.site_limit(user_on_v1) + assert 50 == Plausible.Teams.Adapter.Read.Billing.site_limit(user_on_v2) + assert 50 == Plausible.Teams.Adapter.Read.Billing.site_limit(user_on_v3) end test "returns 50 when user is on free_10k plan" do - user = insert(:user, subscription: build(:subscription, paddle_plan_id: "free_10k")) - assert 50 == Quota.Limits.site_limit(user) + user = new_user() |> subscribe_to_plan("free_10k") + assert 50 == Plausible.Teams.Adapter.Read.Billing.site_limit(user) end test "returns the configured site limit for enterprise plan" do - user = insert(:user) - - enterprise_plan = insert(:enterprise_plan, user_id: user.id, site_limit: 500) - insert(:subscription, user_id: user.id, paddle_plan_id: enterprise_plan.paddle_plan_id) - - assert enterprise_plan.site_limit == Quota.Limits.site_limit(user) + user = new_user() |> subscribe_to_enterprise_plan(site_limit: 500) + assert Plausible.Teams.Adapter.Read.Billing.site_limit(user) == 500 end test "returns 10 when user in on trial" do - user = - insert(:user, - trial_expiry_date: Timex.shift(Timex.now(), days: 7) - ) - - assert 10 == Quota.Limits.site_limit(user) + user = new_user(trial_expiry_date: Date.shift(Date.utc_today(), day: 7)) + assert Plausible.Teams.Adapter.Read.Billing.site_limit(user) == 10 end test "returns the subscription limit for enterprise users who have not paid yet" do user = - insert(:user, - enterprise_plan: build(:enterprise_plan, paddle_plan_id: "123321"), - subscription: build(:subscription, paddle_plan_id: @v1_plan_id) - ) + new_user() + |> subscribe_to_plan(@v1_plan_id) + |> subscribe_to_enterprise_plan(paddle_plan_id: "123321", subscription?: false) - assert 50 == Quota.Limits.site_limit(user) + assert Plausible.Teams.Adapter.Read.Billing.site_limit(user) == 50 end test "returns 10 for enterprise users who have not upgraded yet and are on trial" do user = - insert(:user, - enterprise_plan: build(:enterprise_plan, paddle_plan_id: "123321"), - subscription: nil - ) + new_user() |> subscribe_to_enterprise_plan(paddle_plan_id: "123321", subscription?: false) - assert 10 == Quota.Limits.site_limit(user) + assert Plausible.Teams.Adapter.Read.Billing.site_limit(user) == 10 end end 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 955d5d4c74b5..1307049d3a7e 100644 --- a/test/plausible_web/controllers/api/external_sites_controller_test.exs +++ b/test/plausible_web/controllers/api/external_sites_controller_test.exs @@ -2,6 +2,7 @@ defmodule PlausibleWeb.Api.ExternalSitesControllerTest do use Plausible use PlausibleWeb.ConnCase, async: false use Plausible.Repo + use Plausible.Teams.Test on_ee do setup :create_user @@ -77,7 +78,7 @@ defmodule PlausibleWeb.Api.ExternalSitesControllerTest do end test "does not allow creating more sites than the limit", %{conn: conn, user: user} do - insert_list(50, :site, members: [user]) + for _ <- 1..10, do: new_site(owner: user) conn = post(conn, "/api/v1/sites", %{ diff --git a/test/plausible_web/controllers/site_controller_test.exs b/test/plausible_web/controllers/site_controller_test.exs index 0962a49ad4ea..d972d723c1b9 100644 --- a/test/plausible_web/controllers/site_controller_test.exs +++ b/test/plausible_web/controllers/site_controller_test.exs @@ -284,7 +284,7 @@ defmodule PlausibleWeb.SiteControllerTest do conn: conn, user: user } do - insert(:site, members: [user]) + new_site(owner: user) post(conn, "/sites", %{ "site" => %{ @@ -301,7 +301,7 @@ defmodule PlausibleWeb.SiteControllerTest do conn: conn, user: user } do - insert_list(10, :site, members: [user]) + for _ <- 1..10, do: new_site(owner: user) conn = post(conn, "/sites", %{ diff --git a/test/support/teams/test.ex b/test/support/teams/test.ex index f1695ae1680f..b5755625ae3d 100644 --- a/test/support/teams/test.ex +++ b/test/support/teams/test.ex @@ -139,6 +139,32 @@ defmodule Plausible.Teams.Test do {:ok, team} = Teams.get_or_create(user) insert(:growth_subscription, user: user, team: team) + user + end + + def subscribe_to_plan(user, paddle_plan_id) do + {:ok, team} = Teams.get_or_create(user) + + insert(:subscription, user: user, team: team, paddle_plan_id: paddle_plan_id) + user + end + + def subscribe_to_enterprise_plan(user, attrs) do + {:ok, team} = Teams.get_or_create(user) + + {subscription?, attrs} = Keyword.pop(attrs, :subscription?, true) + + enterprise_plan = insert(:enterprise_plan, Keyword.merge([user: user, team: team], attrs)) + + if subscription? do + insert(:subscription, + team: team, + user: user, + paddle_plan_id: enterprise_plan.paddle_plan_id + ) + end + + user end def assert_team_exists(user, team_id \\ nil) do diff --git a/test/test_helper.exs b/test/test_helper.exs index c2115df9ca19..aec1fc2a1f0c 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -9,6 +9,7 @@ Application.ensure_all_started(:double) FunWithFlags.enable(:channels) # Temporary flag to test `read_team_schemas` flag on all tests. if System.get_env("TEST_READ_TEAM_SCHEMAS") == "1" do + IO.puts("READS TEAM SCHEMAS") FunWithFlags.enable(:read_team_schemas) else FunWithFlags.disable(:read_team_schemas)