mirror of
https://github.com/plausible/analytics.git
synced 2024-11-25 07:06:11 +03:00
Extract schema transitions under delegated namespace (#4788)
* Extract schema transitions under delegated namespace * fixup
This commit is contained in:
parent
799e163eef
commit
78a95eb8fc
@ -13,8 +13,6 @@ defmodule Plausible.Sites do
|
|||||||
|
|
||||||
require Plausible.Site.UserPreference
|
require Plausible.Site.UserPreference
|
||||||
|
|
||||||
@type list_opt() :: {:filter_by_domain, String.t()}
|
|
||||||
|
|
||||||
def get_by_domain(domain) do
|
def get_by_domain(domain) do
|
||||||
Repo.get_by(Site, domain: domain)
|
Repo.get_by(Site, domain: domain)
|
||||||
end
|
end
|
||||||
@ -71,109 +69,10 @@ defmodule Plausible.Sites do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def list(user, pagination_params, opts \\ []) do
|
defdelegate list(user, pagination_params, opts \\ []), to: Plausible.Teams.Adapter.Read.Sites
|
||||||
if Plausible.Teams.read_team_schemas?(user) do
|
|
||||||
Plausible.Teams.Sites.list(user, pagination_params, opts)
|
|
||||||
else
|
|
||||||
old_list(user, pagination_params, opts)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def list_with_invitations(user, pagination_params, opts \\ []) do
|
defdelegate list_with_invitations(user, pagination_params, opts \\ []),
|
||||||
if Plausible.Teams.read_team_schemas?(user) do
|
to: Plausible.Teams.Adapter.Read.Sites
|
||||||
Plausible.Teams.Sites.list_with_invitations(user, pagination_params, opts)
|
|
||||||
else
|
|
||||||
old_list_with_invitations(user, pagination_params, opts)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec old_list(Auth.User.t(), map(), [list_opt()]) :: Scrivener.Page.t()
|
|
||||||
def old_list(user, pagination_params, opts \\ []) do
|
|
||||||
domain_filter = Keyword.get(opts, :filter_by_domain)
|
|
||||||
|
|
||||||
from(s in Site,
|
|
||||||
left_join: up in Site.UserPreference,
|
|
||||||
on: up.site_id == s.id and up.user_id == ^user.id,
|
|
||||||
inner_join: sm in assoc(s, :memberships),
|
|
||||||
on: sm.user_id == ^user.id,
|
|
||||||
select: %{
|
|
||||||
s
|
|
||||||
| pinned_at: selected_as(up.pinned_at, :pinned_at),
|
|
||||||
entry_type:
|
|
||||||
selected_as(
|
|
||||||
fragment(
|
|
||||||
"""
|
|
||||||
CASE
|
|
||||||
WHEN ? IS NOT NULL THEN 'pinned_site'
|
|
||||||
ELSE 'site'
|
|
||||||
END
|
|
||||||
""",
|
|
||||||
up.pinned_at
|
|
||||||
),
|
|
||||||
:entry_type
|
|
||||||
)
|
|
||||||
},
|
|
||||||
order_by: [asc: selected_as(:entry_type), desc: selected_as(:pinned_at), asc: s.domain],
|
|
||||||
preload: [memberships: sm]
|
|
||||||
)
|
|
||||||
|> maybe_filter_by_domain(domain_filter)
|
|
||||||
|> 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
|
|
||||||
domain_filter = Keyword.get(opts, :filter_by_domain)
|
|
||||||
|
|
||||||
result =
|
|
||||||
from(s in Site,
|
|
||||||
left_join: up in Site.UserPreference,
|
|
||||||
on: up.site_id == s.id and up.user_id == ^user.id,
|
|
||||||
left_join: i in assoc(s, :invitations),
|
|
||||||
on: i.email == ^user.email,
|
|
||||||
left_join: sm in assoc(s, :memberships),
|
|
||||||
on: sm.user_id == ^user.id,
|
|
||||||
where: not is_nil(sm.id) or not is_nil(i.id),
|
|
||||||
select: %{
|
|
||||||
s
|
|
||||||
| pinned_at: selected_as(up.pinned_at, :pinned_at),
|
|
||||||
entry_type:
|
|
||||||
selected_as(
|
|
||||||
fragment(
|
|
||||||
"""
|
|
||||||
CASE
|
|
||||||
WHEN ? IS NOT NULL THEN 'invitation'
|
|
||||||
WHEN ? IS NOT NULL THEN 'pinned_site'
|
|
||||||
ELSE 'site'
|
|
||||||
END
|
|
||||||
""",
|
|
||||||
i.id,
|
|
||||||
up.pinned_at
|
|
||||||
),
|
|
||||||
:entry_type
|
|
||||||
)
|
|
||||||
},
|
|
||||||
order_by: [asc: selected_as(:entry_type), desc: selected_as(:pinned_at), asc: s.domain],
|
|
||||||
preload: [memberships: sm, invitations: i]
|
|
||||||
)
|
|
||||||
|> maybe_filter_by_domain(domain_filter)
|
|
||||||
|> Repo.paginate(pagination_params)
|
|
||||||
|
|
||||||
# Populating `site` preload on `invitation`
|
|
||||||
# without requesting it from database.
|
|
||||||
# Necessary for invitation modals logic.
|
|
||||||
entries =
|
|
||||||
Enum.map(result.entries, fn
|
|
||||||
%{invitations: [invitation]} = site ->
|
|
||||||
site = %{site | invitations: [], memberships: []}
|
|
||||||
invitation = %{invitation | site: site}
|
|
||||||
%{site | invitations: [invitation]}
|
|
||||||
|
|
||||||
site ->
|
|
||||||
site
|
|
||||||
end)
|
|
||||||
|
|
||||||
%{result | entries: entries}
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec for_user_query(Auth.User.t()) :: Ecto.Query.t()
|
@spec for_user_query(Auth.User.t()) :: Ecto.Query.t()
|
||||||
def for_user_query(user) do
|
def for_user_query(user) do
|
||||||
@ -184,13 +83,6 @@ defmodule Plausible.Sites do
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_filter_by_domain(query, domain)
|
|
||||||
when byte_size(domain) >= 1 and byte_size(domain) <= 64 do
|
|
||||||
where(query, [s], ilike(s.domain, ^"%#{domain}%"))
|
|
||||||
end
|
|
||||||
|
|
||||||
defp maybe_filter_by_domain(query, _), do: query
|
|
||||||
|
|
||||||
def create(user, params) do
|
def create(user, params) do
|
||||||
with :ok <- Quota.ensure_can_add_new_site(user) do
|
with :ok <- Quota.ensure_can_add_new_site(user) do
|
||||||
Ecto.Multi.new()
|
Ecto.Multi.new()
|
||||||
|
20
lib/plausible/teams/adapter/read/billing.ex
Normal file
20
lib/plausible/teams/adapter/read/billing.ex
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
defmodule Plausible.Teams.Adapter.Read.Billing do
|
||||||
|
@moduledoc """
|
||||||
|
Transition adapter for new schema reads
|
||||||
|
"""
|
||||||
|
alias Plausible.Teams
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Teams.Billing.check_needs_to_upgrade(team)
|
||||||
|
else
|
||||||
|
Plausible.Billing.check_needs_to_upgrade(user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
71
lib/plausible/teams/adapter/read/ownership.ex
Normal file
71
lib/plausible/teams/adapter/read/ownership.ex
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
defmodule Plausible.Teams.Adapter.Read.Ownership do
|
||||||
|
@moduledoc """
|
||||||
|
Transition adapter for new schema reads
|
||||||
|
"""
|
||||||
|
use Plausible
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
missing_features =
|
||||||
|
Plausible.Billing.Quota.Usage.features_usage(nil, [site.id])
|
||||||
|
|> Enum.filter(&(&1.check_availability(user_or_team) != :ok))
|
||||||
|
|
||||||
|
if missing_features == [] do
|
||||||
|
:ok
|
||||||
|
else
|
||||||
|
{:error, {:missing_features, missing_features}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
def check_feature_access(_site, _new_owner) do
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
121
lib/plausible/teams/adapter/read/sites.ex
Normal file
121
lib/plausible/teams/adapter/read/sites.ex
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
defmodule Plausible.Teams.Adapter.Read.Sites do
|
||||||
|
@moduledoc """
|
||||||
|
Transition adapter for new schema reads
|
||||||
|
"""
|
||||||
|
import Ecto.Query
|
||||||
|
alias Plausible.Repo
|
||||||
|
alias Plausible.Site
|
||||||
|
alias Plausible.Auth
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
domain_filter = Keyword.get(opts, :filter_by_domain)
|
||||||
|
|
||||||
|
from(s in Site,
|
||||||
|
left_join: up in Site.UserPreference,
|
||||||
|
on: up.site_id == s.id and up.user_id == ^user.id,
|
||||||
|
inner_join: sm in assoc(s, :memberships),
|
||||||
|
on: sm.user_id == ^user.id,
|
||||||
|
select: %{
|
||||||
|
s
|
||||||
|
| pinned_at: selected_as(up.pinned_at, :pinned_at),
|
||||||
|
entry_type:
|
||||||
|
selected_as(
|
||||||
|
fragment(
|
||||||
|
"""
|
||||||
|
CASE
|
||||||
|
WHEN ? IS NOT NULL THEN 'pinned_site'
|
||||||
|
ELSE 'site'
|
||||||
|
END
|
||||||
|
""",
|
||||||
|
up.pinned_at
|
||||||
|
),
|
||||||
|
:entry_type
|
||||||
|
)
|
||||||
|
},
|
||||||
|
order_by: [asc: selected_as(:entry_type), desc: selected_as(:pinned_at), asc: s.domain],
|
||||||
|
preload: [memberships: sm]
|
||||||
|
)
|
||||||
|
|> maybe_filter_by_domain(domain_filter)
|
||||||
|
|> 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
|
||||||
|
domain_filter = Keyword.get(opts, :filter_by_domain)
|
||||||
|
|
||||||
|
result =
|
||||||
|
from(s in Site,
|
||||||
|
left_join: up in Site.UserPreference,
|
||||||
|
on: up.site_id == s.id and up.user_id == ^user.id,
|
||||||
|
left_join: i in assoc(s, :invitations),
|
||||||
|
on: i.email == ^user.email,
|
||||||
|
left_join: sm in assoc(s, :memberships),
|
||||||
|
on: sm.user_id == ^user.id,
|
||||||
|
where: not is_nil(sm.id) or not is_nil(i.id),
|
||||||
|
select: %{
|
||||||
|
s
|
||||||
|
| pinned_at: selected_as(up.pinned_at, :pinned_at),
|
||||||
|
entry_type:
|
||||||
|
selected_as(
|
||||||
|
fragment(
|
||||||
|
"""
|
||||||
|
CASE
|
||||||
|
WHEN ? IS NOT NULL THEN 'invitation'
|
||||||
|
WHEN ? IS NOT NULL THEN 'pinned_site'
|
||||||
|
ELSE 'site'
|
||||||
|
END
|
||||||
|
""",
|
||||||
|
i.id,
|
||||||
|
up.pinned_at
|
||||||
|
),
|
||||||
|
:entry_type
|
||||||
|
)
|
||||||
|
},
|
||||||
|
order_by: [asc: selected_as(:entry_type), desc: selected_as(:pinned_at), asc: s.domain],
|
||||||
|
preload: [memberships: sm, invitations: i]
|
||||||
|
)
|
||||||
|
|> maybe_filter_by_domain(domain_filter)
|
||||||
|
|> Repo.paginate(pagination_params)
|
||||||
|
|
||||||
|
# Populating `site` preload on `invitation`
|
||||||
|
# without requesting it from database.
|
||||||
|
# Necessary for invitation modals logic.
|
||||||
|
entries =
|
||||||
|
Enum.map(result.entries, fn
|
||||||
|
%{invitations: [invitation]} = site ->
|
||||||
|
site = %{site | invitations: [], memberships: []}
|
||||||
|
invitation = %{invitation | site: site}
|
||||||
|
%{site | invitations: [invitation]}
|
||||||
|
|
||||||
|
site ->
|
||||||
|
site
|
||||||
|
end)
|
||||||
|
|
||||||
|
%{result | entries: entries}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_filter_by_domain(query, domain)
|
||||||
|
when byte_size(domain) >= 1 and byte_size(domain) <= 64 do
|
||||||
|
where(query, [s], ilike(s.domain, ^"%#{domain}%"))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_filter_by_domain(query, _), do: query
|
||||||
|
end
|
@ -6,11 +6,7 @@ defmodule PlausibleWeb.Live.Sites do
|
|||||||
use PlausibleWeb, :live_view
|
use PlausibleWeb, :live_view
|
||||||
import PlausibleWeb.Live.Components.Pagination
|
import PlausibleWeb.Live.Components.Pagination
|
||||||
|
|
||||||
alias Plausible.Auth
|
|
||||||
alias Plausible.Site
|
|
||||||
alias Plausible.Sites
|
alias Plausible.Sites
|
||||||
alias Plausible.Site.Memberships.Invitations
|
|
||||||
alias Plausible.Teams
|
|
||||||
|
|
||||||
def mount(params, _session, socket) do
|
def mount(params, _session, socket) do
|
||||||
uri =
|
uri =
|
||||||
@ -644,38 +640,11 @@ defmodule PlausibleWeb.Live.Sites do
|
|||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp has_sites?(user) do
|
defdelegate has_sites?(user), to: Plausible.Teams.Adapter.Read.Ownership
|
||||||
if Teams.read_team_schemas?(user) do
|
|
||||||
Teams.Users.has_sites?(user, include_pending?: true)
|
|
||||||
else
|
|
||||||
Site.Memberships.any_or_pending?(user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp owns_sites?(user, sites) do
|
defdelegate owns_sites?(user, sites), to: Plausible.Teams.Adapter.Read.Ownership
|
||||||
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
|
|
||||||
end
|
|
||||||
|
|
||||||
defp check_needs_to_upgrade(user) do
|
defdelegate check_needs_to_upgrade(user), to: Plausible.Teams.Adapter.Read.Billing
|
||||||
if Teams.read_team_schemas?(user) do
|
|
||||||
team =
|
|
||||||
case Teams.get_by_owner(user) do
|
|
||||||
{:ok, team} -> team
|
|
||||||
{:error, _} -> nil
|
|
||||||
end
|
|
||||||
|
|
||||||
Teams.Billing.check_needs_to_upgrade(team)
|
|
||||||
else
|
|
||||||
Plausible.Billing.check_needs_to_upgrade(user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp load_sites(%{assigns: assigns} = socket) do
|
defp load_sites(%{assigns: assigns} = socket) do
|
||||||
sites =
|
sites =
|
||||||
@ -726,32 +695,12 @@ defmodule PlausibleWeb.Live.Sites do
|
|||||||
|
|
||||||
defp check_limits(invitation, _), do: %{invitation: invitation}
|
defp check_limits(invitation, _), do: %{invitation: invitation}
|
||||||
|
|
||||||
defp ensure_can_take_ownership(site, user) do
|
defdelegate ensure_can_take_ownership(site, user), to: Plausible.Teams.Adapter.Read.Ownership
|
||||||
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)
|
defdelegate check_feature_access(site, user), to: Plausible.Teams.Adapter.Read.Ownership
|
||||||
else
|
|
||||||
Invitations.ensure_can_take_ownership(site, user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp check_features(%{role: :owner, site: site} = invitation, user) do
|
def check_features(%{role: :owner, site: site} = invitation, user) do
|
||||||
user_or_team =
|
case check_feature_access(site, user) do
|
||||||
if Teams.read_team_schemas?(user) do
|
|
||||||
case Teams.get_by_owner(user) do
|
|
||||||
{:ok, team} -> team
|
|
||||||
{:error, _} -> nil
|
|
||||||
end
|
|
||||||
else
|
|
||||||
user
|
|
||||||
end
|
|
||||||
|
|
||||||
case check_feature_access(site, user_or_team) do
|
|
||||||
:ok ->
|
:ok ->
|
||||||
%{invitation: invitation}
|
%{invitation: invitation}
|
||||||
|
|
||||||
@ -765,24 +714,6 @@ defmodule PlausibleWeb.Live.Sites do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if ce?() do
|
|
||||||
defp check_feature_access(_site, _new_owner) do
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
else
|
|
||||||
defp check_feature_access(site, new_owner) do
|
|
||||||
missing_features =
|
|
||||||
Plausible.Billing.Quota.Usage.features_usage(nil, [site.id])
|
|
||||||
|> Enum.filter(&(&1.check_availability(new_owner) != :ok))
|
|
||||||
|
|
||||||
if missing_features == [] do
|
|
||||||
:ok
|
|
||||||
else
|
|
||||||
{:error, {:missing_features, missing_features}}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp set_filter_text(socket, filter_text) do
|
defp set_filter_text(socket, filter_text) do
|
||||||
uri = socket.assigns.uri
|
uri = socket.assigns.uri
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user