Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Switch reads in invitation creation logic to teams behind FF + loosely related fixes 🍌 #4845

Merged
merged 13 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 40 additions & 13 deletions lib/plausible/site/memberships/create_invitation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ defmodule Plausible.Site.Memberships.CreateInvitation do

alias Plausible.Auth.{User, Invitation}
alias Plausible.{Site, Sites, Site.Membership}
alias Plausible.Site.Memberships.Invitations
alias Plausible.Billing.Quota
import Ecto.Query
use Plausible
Expand Down Expand Up @@ -73,24 +72,49 @@ defmodule Plausible.Site.Memberships.CreateInvitation do
attrs = %{email: invitee_email, role: role, site_id: site.id, inviter_id: inviter.id}

with site <- Plausible.Repo.preload(site, :owner),
:ok <- check_invitation_permissions(site, inviter, role, opts),
:ok <- check_team_member_limit(site, role, invitee_email),
:ok <-
Plausible.Teams.Adapter.Read.Invitations.check_invitation_permissions(
site,
inviter,
role,
opts
),
:ok <-
Plausible.Teams.Adapter.Read.Invitations.check_team_member_limit(
inviter,
site,
role,
invitee_email
),
invitee = Plausible.Auth.find_user_by(email: invitee_email),
:ok <- Invitations.ensure_transfer_valid(site, invitee, role),
:ok <- ensure_new_membership(site, invitee, role),
:ok <-
Plausible.Teams.Adapter.Read.Invitations.ensure_transfer_valid(
inviter,
site,
invitee,
role
),
:ok <-
Plausible.Teams.Adapter.Read.Invitations.ensure_new_membership(
inviter,
site,
invitee,
role
),
%Ecto.Changeset{} = changeset <- Invitation.new(attrs),
{:ok, invitation} <- Plausible.Repo.insert(changeset) do
send_invitation_email(invitation, invitee)

Plausible.Teams.Invitations.invite_sync(site, invitation)

Plausible.Teams.Adapter.Read.Invitations.send_invitation_email(inviter, invitation, invitee)

invitation
else
{:error, cause} -> Plausible.Repo.rollback(cause)
end
end

defp check_invitation_permissions(site, inviter, requested_role, opts) do
@doc false
def check_invitation_permissions(site, inviter, requested_role, opts) do
check_permissions? = Keyword.get(opts, :check_permissions, true)

if check_permissions? do
Expand All @@ -107,7 +131,8 @@ defmodule Plausible.Site.Memberships.CreateInvitation do
end
end

defp send_invitation_email(invitation, invitee) do
@doc false
def send_invitation_email(invitation, invitee) do
invitation = Plausible.Repo.preload(invitation, [:site, :inviter])

email =
Expand Down Expand Up @@ -140,23 +165,25 @@ defmodule Plausible.Site.Memberships.CreateInvitation do
Plausible.Mailer.send(email)
end

defp ensure_new_membership(_site, _invitee, :owner) do
@doc false
def ensure_new_membership(_site, _invitee, :owner) do
:ok
end

defp ensure_new_membership(site, invitee, _role) do
def ensure_new_membership(site, invitee, _role) do
if invitee && Sites.is_member?(invitee.id, site) do
{:error, :already_a_member}
else
:ok
end
end

defp check_team_member_limit(_site, :owner, _invitee_email) do
@doc false
def check_team_member_limit(_site, :owner, _invitee_email) do
:ok
end

defp check_team_member_limit(site, _role, invitee_email) do
def check_team_member_limit(site, _role, invitee_email) do
site = Plausible.Repo.preload(site, :owner)
limit = Quota.Limits.team_member_limit(site.owner)
usage = Quota.Usage.team_member_usage(site.owner, exclude_emails: [invitee_email])
Expand Down
4 changes: 3 additions & 1 deletion lib/plausible/teams.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ defmodule Plausible.Teams do

def on_trial?(team) do
team = with_subscription(team)
not Plausible.Billing.Subscriptions.active?(team.subscription) && trial_days_left(team) >= 0

not Plausible.Billing.Subscriptions.active?(team.subscription) &&
trial_days_left(team) >= 0
end
else
def on_trial?(_), do: true
Expand Down
14 changes: 14 additions & 0 deletions lib/plausible/teams/adapter/read/billing.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ defmodule Plausible.Teams.Adapter.Read.Billing do
"""
use Plausible.Teams.Adapter

def quota_usage(user, opts \\ []) do
switch(user,
team_fn: &Plausible.Teams.Billing.quota_usage(&1, opts),
user_fn: &Plausible.Billing.Quota.Usage.usage(&1, opts)
)
end

def allow_next_upgrade_override?(user) do
switch(user,
team_fn: &(&1 && &1.allow_next_upgrade_override),
user_fn: & &1.allow_next_upgrade_override
)
end

def change_plan(user, new_plan_id) do
switch(user,
team_fn: &Plausible.Teams.Billing.change_plan(&1, new_plan_id),
Expand Down
120 changes: 120 additions & 0 deletions lib/plausible/teams/adapter/read/invitations.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
defmodule Plausible.Teams.Adapter.Read.Invitations do
@moduledoc """
Transition adapter for new schema reads
"""
use Plausible
use Plausible.Teams.Adapter

alias Plausible.Repo

def check_invitation_permissions(site, inviter, role, opts) do
switch(
inviter,
team_fn: fn _ ->
Plausible.Teams.Invitations.check_invitation_permissions(
site,
inviter,
role,
opts
)
end,
user_fn: fn _ ->
Plausible.Site.Memberships.CreateInvitation.check_invitation_permissions(
site,
inviter,
role,
opts
)
end
)
end

def check_team_member_limit(inviter, site, role, invitee_email) do
switch(
inviter,
team_fn: fn _ ->
site_team = Repo.preload(site, :team).team

Plausible.Teams.Invitations.check_team_member_limit(
site_team,
role,
invitee_email
)
end,
user_fn: fn _ ->
Plausible.Site.Memberships.CreateInvitation.check_team_member_limit(
site,
role,
invitee_email
)
end
)
end

def ensure_transfer_valid(inviter, site, invitee, role) do
switch(
inviter,
team_fn: fn _ ->
site_team = Repo.preload(site, :team).team

Plausible.Teams.Invitations.ensure_transfer_valid(
site_team,
invitee,
role
)
end,
user_fn: fn _ ->
Plausible.Site.Memberships.Invitations.ensure_transfer_valid(
site,
invitee,
role
)
end
)
end

def ensure_new_membership(inviter, site, invitee, role) do
switch(
inviter,
team_fn: fn _ ->
Plausible.Teams.Invitations.ensure_new_membership(
site,
invitee,
role
)
end,
user_fn: fn _ ->
Plausible.Site.Memberships.CreateInvitation.ensure_new_membership(
site,
invitee,
role
)
end
)
end

def send_invitation_email(inviter, invitation, invitee) do
switch(
inviter,
team_fn: fn _ ->
if invitation.role == :owner do
Teams.SiteTransfer
|> Repo.get_by!(transfer_id: invitation.invitation_id, initiator_id: inviter.id)
|> Repo.preload([:site, :initiator])
|> Plausible.Teams.Invitations.send_invitation_email(invitee)
else
Teams.GuestInvitation
|> Repo.get_by!(invitation_id: invitation.invitation_id)
|> Repo.preload([:site, team_invitation: :inviter])
|> Plausible.Teams.Invitations.send_invitation_email(invitee)
end
end,
user_fn: fn _ ->
Plausible.Site.Memberships.CreateInvitation.send_invitation_email(
invitation,
invitee
)
end
)
end
end
27 changes: 27 additions & 0 deletions lib/plausible/teams/adapter/read/teams.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
defmodule Plausible.Teams.Adapter.Read.Teams do
@moduledoc """
Transition adapter for new schema reads
"""
use Plausible.Teams.Adapter

def trial_expiry_date(user) do
switch(user,
team_fn: &(&1 && &1.trial_expiry_date),
user_fn: & &1.trial_expiry_date
)
end

def on_trial?(user) do
switch(user,
team_fn: &Plausible.Teams.on_trial?/1,
user_fn: &Plausible.Users.on_trial?/1
)
end

def trial_days_left(user) do
switch(user,
team_fn: &Plausible.Teams.trial_days_left/1,
user_fn: &Plausible.Users.trial_days_left/1
)
end
end
41 changes: 31 additions & 10 deletions lib/plausible/teams/billing.ex
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,9 @@ defmodule Plausible.Teams.Billing do
end

def team_member_usage(team, opts) do
exclude_emails = Keyword.get(opts, :exclude_emails, [])
{:ok, owner} = Teams.Sites.get_owner(team)
exclude_emails = Keyword.get(opts, :exclude_emails, []) ++ [owner.email]

pending_site_ids = Keyword.get(opts, :pending_ownership_site_ids, [])

team
Expand Down Expand Up @@ -320,7 +322,7 @@ defmodule Plausible.Teams.Billing do
}
end

def features_usage(user, site_ids \\ nil)
def features_usage(team, site_ids \\ nil)

def features_usage(%Teams.Team{} = team, nil) do
owned_site_ids = team |> Teams.owned_sites() |> Enum.map(& &1.id)
Expand All @@ -331,8 +333,16 @@ defmodule Plausible.Teams.Billing do
site_scoped_feature_usage = features_usage(nil, owned_site_ids)

stats_api_used? =
from(a in Plausible.Auth.ApiKey, where: a.team_id == ^team.id)
|> Plausible.Repo.exists?()
Plausible.Repo.exists?(
from tm in Plausible.Teams.Membership,
as: :team_membership,
where: tm.team_id == ^team.id,
where:
exists(
from ak in Plausible.Auth.ApiKey,
where: ak.user_id == parent_as(:team_membership).user_id
)
)

if stats_api_used? do
site_scoped_feature_usage ++ [Feature.StatsAPI]
Expand All @@ -345,36 +355,47 @@ defmodule Plausible.Teams.Billing do
Plausible.Billing.Quota.Usage.features_usage(nil, owned_site_ids)
end

defp query_team_member_emails(team, site_ids, exclude_emails) do
defp query_team_member_emails(team, pending_ownership_site_ids, exclude_emails) do
pending_owner_memberships_q =
from s in Plausible.Site,
inner_join: t in assoc(s, :team),
inner_join: tm in assoc(t, :team_memberships),
inner_join: u in assoc(tm, :user),
where: s.id in ^pending_ownership_site_ids,
where: tm.role == :owner,
where: u.email not in ^exclude_emails,
select: %{email: u.email}

pending_memberships_q =
from tm in Teams.Membership,
inner_join: u in assoc(tm, :user),
inner_join: gm in assoc(tm, :guest_memberships),
where: gm.site_id in ^site_ids and tm.role != :owner,
left_join: gm in assoc(tm, :guest_memberships),
where: gm.site_id in ^pending_ownership_site_ids,
where: u.email not in ^exclude_emails,
select: %{email: u.email}

pending_invitations_q =
from ti in Teams.Invitation,
inner_join: gi in assoc(ti, :guest_invitations),
where: gi.site_id in ^site_ids and ti.role != :owner,
where: gi.site_id in ^pending_ownership_site_ids,
where: ti.email not in ^exclude_emails,
select: %{email: ti.email}

team_memberships_q =
from tm in Teams.Membership,
inner_join: u in assoc(tm, :user),
where: tm.team_id == ^team.id and tm.role != :owner,
where: tm.team_id == ^team.id,
where: u.email not in ^exclude_emails,
select: %{email: u.email}

team_invitations_q =
from ti in Teams.Invitation,
where: ti.team_id == ^team.id and ti.role != :owner,
where: ti.team_id == ^team.id,
where: ti.email not in ^exclude_emails,
select: %{email: ti.email}

pending_memberships_q
|> union(^pending_owner_memberships_q)
|> union(^pending_invitations_q)
|> union(^team_memberships_q)
|> union(^team_invitations_q)
Expand Down
Loading
Loading