analytics/lib/plausible/site/memberships/invitations.ex

130 lines
3.9 KiB
Elixir

defmodule Plausible.Site.Memberships.Invitations do
@moduledoc false
use Plausible
import Ecto.Query, only: [from: 2]
alias Plausible.Site
alias Plausible.Auth
alias Plausible.Repo
alias Plausible.Billing.Quota
alias Plausible.Billing.Feature
@type missing_features_error() :: {:missing_features, [Feature.t()]}
@spec find_for_user(String.t(), Auth.User.t()) ::
{:ok, Auth.Invitation.t()} | {:error, :invitation_not_found}
def find_for_user(invitation_id, user) do
invitation =
Auth.Invitation
|> Repo.get_by(invitation_id: invitation_id, email: user.email)
|> Repo.preload([:site, :inviter])
if invitation do
{:ok, invitation}
else
{:error, :invitation_not_found}
end
end
@spec find_for_site(String.t(), Plausible.Site.t()) ::
{:ok, Auth.Invitation.t()} | {:error, :invitation_not_found}
def find_for_site(invitation_id, site) do
invitation =
Auth.Invitation
|> Repo.get_by(invitation_id: invitation_id, site_id: site.id)
|> Repo.preload([:site, :inviter])
if invitation do
{:ok, invitation}
else
{:error, :invitation_not_found}
end
end
@spec delete_invitation(Auth.Invitation.t()) :: :ok
def delete_invitation(invitation) do
Repo.delete_all(from(i in Auth.Invitation, where: i.id == ^invitation.id))
:ok
end
@spec ensure_transfer_valid(Site.t(), Auth.User.t() | nil, Site.Membership.role()) ::
:ok | {:error, :transfer_to_self}
def ensure_transfer_valid(%Site{} = site, %Auth.User{} = new_owner, :owner) do
if Plausible.Sites.role(new_owner.id, site) == :owner do
{:error, :transfer_to_self}
else
:ok
end
end
def ensure_transfer_valid(_site, _invitee, _role) do
:ok
end
on_full_build do
@spec ensure_can_take_ownership(Site.t(), Auth.User.t()) ::
:ok | {:error, Quota.over_limits_error() | :no_plan}
def ensure_can_take_ownership(site, new_owner) do
site = Repo.preload(site, :owner)
new_owner = Plausible.Users.with_subscription(new_owner)
plan = Plausible.Billing.Plans.get_subscription_plan(new_owner.subscription)
active_subscription? = Plausible.Billing.subscription_is_active?(new_owner.subscription)
if active_subscription? && plan != :free_10k do
usage_after_transfer = %{
monthly_pageviews: monthly_pageview_usage_after_transfer(site, new_owner),
team_members: team_member_usage_after_transfer(site, new_owner),
sites: Quota.site_usage(new_owner) + 1
}
Quota.ensure_within_plan_limits(usage_after_transfer, plan)
else
{:error, :no_plan}
end
end
defp team_member_usage_after_transfer(site, new_owner) do
current_usage = Quota.team_member_usage(new_owner)
site_usage = Repo.aggregate(Quota.team_member_usage_query(site.owner, site), :count)
extra_usage =
if Plausible.Sites.is_member?(new_owner.id, site), do: 0, else: 1
current_usage + site_usage + extra_usage
end
defp monthly_pageview_usage_after_transfer(site, new_owner) do
site_ids = Plausible.Sites.owned_site_ids(new_owner) ++ [site.id]
Quota.monthly_pageview_usage(new_owner, site_ids)
end
else
@spec ensure_can_take_ownership(Site.t(), Auth.User.t()) :: :ok
def ensure_can_take_ownership(_site, _new_owner) do
:ok
end
end
@spec check_feature_access(Site.t(), Auth.User.t(), boolean()) ::
:ok | {:error, missing_features_error()}
def check_feature_access(_site, _new_owner, true = _selfhost?) do
:ok
end
def check_feature_access(site, new_owner, false = _selfhost?) do
missing_features =
site
|> Quota.features_usage()
|> Enum.filter(&(&1.check_availability(new_owner) != :ok))
if missing_features == [] do
:ok
else
{:error, {:missing_features, missing_features}}
end
end
end