Skip to content

Commit 95471c0

Browse files
zoldaraerosol
andauthored
Switch AcceptInvitation ops to read from team schemas behind FF (#4847)
* Move `bulk_transfer_ownership_direct` under `AcceptInvitation` * [WIP] Switch ownership transfer operations to read from team schemas behind FF * Fix usage test regression * Semantics - current user; ownership is not necessarily involved * Perform remaining read via adapter; remove obsolete test * Properly list site with pending site transfer while being guest on a team * Account for pending site transfers in Settings > People list --------- Co-authored-by: Adam Rutkowski <hq@mtod.org>
1 parent 8dad965 commit 95471c0

15 files changed

Lines changed: 426 additions & 654 deletions

File tree

lib/plausible/site/admin.ex

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,9 +131,16 @@ defmodule Plausible.SiteAdmin do
131131
{:error, "Please select at least one site from the list"}
132132
end
133133

134-
defp transfer_ownership_direct(_conn, sites, %{"email" => email}) do
134+
defp transfer_ownership_direct(conn, sites, %{"email" => email}) do
135+
current_user = conn.assigns.current_user
136+
135137
with {:ok, new_owner} <- Plausible.Auth.get_user_by(email: email),
136-
{:ok, _} <- Plausible.Site.Memberships.bulk_transfer_ownership_direct(sites, new_owner) do
138+
{:ok, _} <-
139+
Plausible.Site.Memberships.bulk_transfer_ownership_direct(
140+
current_user,
141+
sites,
142+
new_owner
143+
) do
137144
:ok
138145
else
139146
{:error, :user_not_found} ->

lib/plausible/site/memberships.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ defmodule Plausible.Site.Memberships do
99
alias Plausible.Repo
1010
alias Plausible.Site.Memberships
1111

12-
defdelegate transfer_ownership(site, user), to: Memberships.AcceptInvitation
1312
defdelegate accept_invitation(invitation_id, user), to: Memberships.AcceptInvitation
1413
defdelegate reject_invitation(invitation_id, user), to: Memberships.RejectInvitation
1514
defdelegate remove_invitation(invitation_id, site), to: Memberships.RemoveInvitation
@@ -20,7 +19,8 @@ defmodule Plausible.Site.Memberships do
2019
defdelegate bulk_create_invitation(sites, inviter, invitee_email, role, opts),
2120
to: Memberships.CreateInvitation
2221

23-
defdelegate bulk_transfer_ownership_direct(sites, new_owner), to: Memberships.CreateInvitation
22+
defdelegate bulk_transfer_ownership_direct(current_user, sites, new_owner),
23+
to: Memberships.AcceptInvitation
2424

2525
@spec any?(Auth.User.t()) :: boolean()
2626
def any?(user) do

lib/plausible/site/memberships/accept_invitation.ex

Lines changed: 63 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -23,43 +23,36 @@ defmodule Plausible.Site.Memberships.AcceptInvitation do
2323

2424
require Logger
2525

26-
@spec transfer_ownership(Site.t(), Auth.User.t()) ::
27-
{:ok, Site.Membership.t()}
28-
| {:error,
29-
Billing.Quota.Limits.over_limits_error()
30-
| Ecto.Changeset.t()
31-
| :transfer_to_self
32-
| :no_plan}
33-
def transfer_ownership(site, user) do
34-
site = Repo.preload(site, :owner)
35-
36-
with :ok <- Invitations.ensure_transfer_valid(site, user, :owner),
37-
:ok <- Invitations.ensure_can_take_ownership(site, user) do
38-
membership = get_or_create_owner_membership(site, user)
39-
40-
multi = add_and_transfer_ownership(site, membership, user)
41-
42-
case Repo.transaction(multi) do
43-
{:ok, changes} ->
44-
Plausible.Teams.Invitations.transfer_site_sync(site, user)
45-
46-
membership = Repo.preload(changes.membership, [:site, :user])
47-
48-
{:ok, membership}
49-
50-
{:error, _operation, error, _changes} ->
51-
{:error, error}
26+
@type transfer_error() ::
27+
Billing.Quota.Limits.over_limits_error()
28+
| Ecto.Changeset.t()
29+
| :transfer_to_self
30+
| :no_plan
31+
32+
@type accept_error() ::
33+
:invitation_not_found
34+
| Billing.Quota.Limits.over_limits_error()
35+
| Ecto.Changeset.t()
36+
| :no_plan
37+
38+
@spec bulk_transfer_ownership_direct(Auth.User.t(), [Site.t()], Auth.User.t()) ::
39+
{:ok, [Site.Membership.t()]} | {:error, transfer_error()}
40+
def bulk_transfer_ownership_direct(current_user, sites, new_owner) do
41+
Repo.transaction(fn ->
42+
for site <- sites do
43+
case transfer_ownership(current_user, site, new_owner) do
44+
{:ok, membership} ->
45+
membership
46+
47+
{:error, error} ->
48+
Repo.rollback(error)
49+
end
5250
end
53-
end
51+
end)
5452
end
5553

5654
@spec accept_invitation(String.t(), Auth.User.t()) ::
57-
{:ok, Site.Membership.t()}
58-
| {:error,
59-
:invitation_not_found
60-
| Billing.Quota.Limits.over_limits_error()
61-
| Ecto.Changeset.t()
62-
| :no_plan}
55+
{:ok, Site.Membership.t()} | {:error, accept_error()}
6356
def accept_invitation(invitation_id, user) do
6457
with {:ok, invitation} <- Invitations.find_for_user(invitation_id, user) do
6558
if invitation.role == :owner do
@@ -70,11 +63,45 @@ defmodule Plausible.Site.Memberships.AcceptInvitation do
7063
end
7164
end
7265

66+
defp transfer_ownership(current_user, site, new_owner) do
67+
with :ok <-
68+
Plausible.Teams.Adapter.Read.Invitations.ensure_transfer_valid(
69+
current_user,
70+
site,
71+
new_owner,
72+
:owner
73+
),
74+
:ok <- Plausible.Teams.Adapter.Read.Ownership.ensure_can_take_ownership(site, new_owner) do
75+
membership = get_or_create_owner_membership(site, new_owner)
76+
77+
multi = add_and_transfer_ownership(site, membership, new_owner)
78+
79+
case Repo.transaction(multi) do
80+
{:ok, changes} ->
81+
Plausible.Teams.Invitations.transfer_site_sync(site, new_owner)
82+
83+
membership = Repo.preload(changes.membership, [:site, :user])
84+
85+
{:ok, membership}
86+
87+
{:error, _operation, error, _changes} ->
88+
{:error, error}
89+
end
90+
end
91+
end
92+
7393
defp do_accept_ownership_transfer(invitation, user) do
7494
membership = get_or_create_membership(invitation, user)
75-
site = Repo.preload(invitation.site, :owner)
76-
77-
with :ok <- Invitations.ensure_can_take_ownership(site, user) do
95+
site = invitation.site
96+
97+
with :ok <-
98+
Plausible.Teams.Adapter.Read.Invitations.ensure_transfer_valid(
99+
user,
100+
site,
101+
user,
102+
:owner
103+
),
104+
:ok <- Plausible.Teams.Adapter.Read.Ownership.ensure_can_take_ownership(site, user) do
78105
site
79106
|> add_and_transfer_ownership(membership, user)
80107
|> Multi.delete(:invitation, invitation)

lib/plausible/site/memberships/create_invitation.ex

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -37,27 +37,6 @@ defmodule Plausible.Site.Memberships.CreateInvitation do
3737
end)
3838
end
3939

40-
@spec bulk_transfer_ownership_direct([Site.t()], User.t()) ::
41-
{:ok, [Membership.t()]}
42-
| {:error,
43-
invite_error()
44-
| Quota.Limits.over_limits_error()}
45-
def bulk_transfer_ownership_direct(sites, new_owner) do
46-
Plausible.Repo.transaction(fn ->
47-
for site <- sites do
48-
site = Plausible.Repo.preload(site, :owner)
49-
50-
case Site.Memberships.transfer_ownership(site, new_owner) do
51-
{:ok, membership} ->
52-
membership
53-
54-
{:error, error} ->
55-
Plausible.Repo.rollback(error)
56-
end
57-
end
58-
end)
59-
end
60-
6140
@spec bulk_create_invitation([Site.t()], User.t(), String.t(), atom(), Keyword.t()) ::
6241
{:ok, [Invitation.t()]} | {:error, invite_error()}
6342
def bulk_create_invitation(sites, inviter, invitee_email, role, opts \\ []) do

lib/plausible/teams/adapter/read/invitations.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,9 @@ defmodule Plausible.Teams.Adapter.Read.Invitations do
5151
)
5252
end
5353

54-
def ensure_transfer_valid(inviter, site, invitee, role) do
54+
def ensure_transfer_valid(current_user, site, invitee, role) do
5555
switch(
56-
inviter,
56+
current_user,
5757
team_fn: fn _ ->
5858
site_team = Repo.preload(site, :team).team
5959

lib/plausible/teams/adapter/read/sites.ex

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,19 @@ defmodule Plausible.Teams.Adapter.Read.Sites do
176176
)
177177
|> Repo.all()
178178

179-
%{memberships: memberships, invitations: invitations}
179+
site_transfers =
180+
from(
181+
st in Teams.SiteTransfer,
182+
where: st.site_id == ^site.id,
183+
select: %Plausible.Auth.Invitation{
184+
invitation_id: st.transfer_id,
185+
email: st.email,
186+
role: :owner
187+
}
188+
)
189+
|> Repo.all()
190+
191+
%{memberships: memberships, invitations: site_transfers ++ invitations}
180192
else
181193
site
182194
|> Repo.preload([:invitations, memberships: :user])

lib/plausible/teams/sites.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ defmodule Plausible.Teams.Sites do
214214
inner_join: u in assoc(tm, :user),
215215
where: tm.team_id == parent_as(:site).team_id,
216216
where: u.email == parent_as(:site_transfer).email,
217+
where: tm.role == :owner,
217218
select: 1
218219
),
219220
select: %{

test/plausible/site/admin_test.exs

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,15 @@ defmodule Plausible.Site.AdminTest do
7575
end
7676

7777
test "new owner must be an existing user", %{conn: conn, transfer_direct_action: action} do
78-
site = insert(:site)
78+
site = new_site()
7979

8080
assert action.(conn, [site], %{"email" => "random@email.com"}) ==
8181
{:error, "User could not be found"}
8282
end
8383

8484
test "new owner can't be the same as old owner", %{conn: conn, transfer_direct_action: action} do
85-
current_owner = insert(:user)
86-
87-
site = insert(:site, members: [current_owner])
85+
current_owner = new_user()
86+
site = new_site(owner: current_owner)
8887

8988
assert {:error, "User is already an owner of one of the sites"} =
9089
action.(conn, [site], %{"email" => current_owner.email})
@@ -96,20 +95,16 @@ defmodule Plausible.Site.AdminTest do
9695
transfer_direct_action: action
9796
} do
9897
today = Date.utc_today()
99-
current_owner = insert(:user)
98+
current_owner = new_user()
10099

101100
new_owner =
102-
insert(:user,
103-
subscription:
104-
build(:growth_subscription,
105-
last_bill_date: Timex.shift(today, days: -5)
106-
)
107-
)
101+
new_user()
102+
|> subscribe_to_growth_plan(last_bill_date: Date.shift(today, day: -5))
108103

109104
# fills the site limit quota
110-
insert_list(10, :site, members: [new_owner])
105+
for _ <- 1..10, do: new_site(owner: new_owner)
111106

112-
site = insert(:site, members: [current_owner])
107+
site = new_site(owner: current_owner)
113108

114109
assert {:error, "Plan limits exceeded" <> _} =
115110
action.(conn, [site], %{"email" => new_owner.email})
@@ -120,18 +115,14 @@ defmodule Plausible.Site.AdminTest do
120115
transfer_direct_action: action
121116
} do
122117
today = Date.utc_today()
123-
current_owner = insert(:user)
118+
current_owner = new_user()
124119

125120
new_owner =
126-
insert(:user,
127-
subscription: build(:subscription, last_bill_date: Timex.shift(today, days: -5))
128-
)
129-
130-
site1 =
131-
insert(:site, memberships: [build(:site_membership, user: current_owner, role: :owner)])
121+
new_user()
122+
|> subscribe_to_growth_plan(last_bill_date: Date.shift(today, day: -5))
132123

133-
site2 =
134-
insert(:site, memberships: [build(:site_membership, user: current_owner, role: :owner)])
124+
site1 = new_site(owner: current_owner)
125+
site2 = new_site(owner: current_owner)
135126

136127
assert :ok = action.(conn, [site1, site2], %{"email" => new_owner.email})
137128
end

0 commit comments

Comments
 (0)