diff --git a/lib/plausible/data_migration/backfill_teams.ex b/lib/plausible/data_migration/backfill_teams.ex
index 5ffe865a7..eb1aee2c6 100644
--- a/lib/plausible/data_migration/backfill_teams.ex
+++ b/lib/plausible/data_migration/backfill_teams.ex
@@ -422,8 +422,9 @@ defmodule Plausible.DataMigration.BackfillTeams do
where: ti.role == :guest,
where:
(gi.role == :viewer and si.role == :admin) or
- (gi.role == :editor and si.role == :viewer),
- select: {gi, si.role}
+ (gi.role == :editor and si.role == :viewer) or
+ is_distinct(gi.invitation_id, si.invitation_id),
+ select: {gi, si}
)
|> @repo.all(timeout: :infinity)
@@ -770,7 +771,6 @@ defmodule Plausible.DataMigration.BackfillTeams do
role: :guest,
inviter: first_site_invitation.inviter
)
- |> Ecto.Changeset.put_change(:invitation_id, first_site_invitation.invitation_id)
|> Ecto.Changeset.put_change(:inserted_at, first_site_invitation.inserted_at)
|> Ecto.Changeset.put_change(:updated_at, first_site_invitation.updated_at)
|> @repo.insert!(
@@ -784,6 +784,7 @@ defmodule Plausible.DataMigration.BackfillTeams do
site_invitation.site,
translate_role(site_invitation.role)
)
+ |> Ecto.Changeset.put_change(:invitation_id, site_invitation.invitation_id)
|> Ecto.Changeset.put_change(:inserted_at, site_invitation.inserted_at)
|> Ecto.Changeset.put_change(:updated_at, site_invitation.updated_at)
|> @repo.insert!()
@@ -795,12 +796,14 @@ defmodule Plausible.DataMigration.BackfillTeams do
end)
end
- defp sync_guest_invitations(guest_invitations_and_roles) do
- guest_invitations_and_roles
+ defp sync_guest_invitations(guest_and_site_invitations) do
+ guest_and_site_invitations
|> Enum.with_index()
- |> Enum.each(fn {{guest_invitation, role}, idx} ->
+ |> Enum.each(fn {{guest_invitation, site_invitation}, idx} ->
guest_invitation
- |> Ecto.Changeset.change(role: translate_role(role))
+ |> Ecto.Changeset.change()
+ |> Ecto.Changeset.put_change(:role, translate_role(site_invitation.role))
+ |> Ecto.Changeset.put_change(:invitation_id, site_invitation.invitation_id)
|> Ecto.Changeset.put_change(:updated_at, guest_invitation.updated_at)
|> @repo.update!()
diff --git a/lib/plausible/data_migration/teams_consistency_check.ex b/lib/plausible/data_migration/teams_consistency_check.ex
index 63e59f742..e192b0405 100644
--- a/lib/plausible/data_migration/teams_consistency_check.ex
+++ b/lib/plausible/data_migration/teams_consistency_check.ex
@@ -290,6 +290,7 @@ defmodule Plausible.DataMigration.TeamsConsitencyCheck do
where:
(i.role == :viewer and parent_as(:guest_invitation).role == :viewer) or
(i.role == :admin and parent_as(:guest_invitation).role == :editor),
+ where: i.invitation_id == parent_as(:guest_invitation).invitation_id,
select: 1
)
diff --git a/lib/plausible/teams/adapter/read/sites.ex b/lib/plausible/teams/adapter/read/sites.ex
index 173394c20..76ec43022 100644
--- a/lib/plausible/teams/adapter/read/sites.ex
+++ b/lib/plausible/teams/adapter/read/sites.ex
@@ -1,11 +1,14 @@
defmodule Plausible.Teams.Adapter.Read.Sites do
@moduledoc """
- Transition adapter for new schema reads
+ Transition adapter for new schema reads
"""
+
import Ecto.Query
+
+ alias Plausible.Auth
alias Plausible.Repo
alias Plausible.Site
- alias Plausible.Auth
+ alias Plausible.Teams
def list(user, pagination_params, opts \\ []) do
if Plausible.Teams.read_team_schemas?(user) do
@@ -112,6 +115,75 @@ defmodule Plausible.Teams.Adapter.Read.Sites do
%{result | entries: entries}
end
+ def list_people(site, user) do
+ if Plausible.Teams.read_team_schemas?(user) do
+ owner_membership =
+ from(
+ tm in Teams.Membership,
+ where: tm.team_id == ^site.team_id,
+ where: tm.role == :owner,
+ select: %Plausible.Site.Membership{
+ user_id: tm.user_id,
+ role: tm.role
+ }
+ )
+ |> Repo.one!()
+
+ memberships =
+ from(
+ gm in Teams.GuestMembership,
+ inner_join: tm in assoc(gm, :team_membership),
+ where: gm.site_id == ^site.id,
+ select: %Plausible.Site.Membership{
+ user_id: tm.user_id,
+ role:
+ fragment(
+ """
+ CASE
+ WHEN ? = 'editor' THEN 'admin'
+ ELSE ?
+ END
+ """,
+ gm.role,
+ gm.role
+ )
+ }
+ )
+ |> Repo.all()
+
+ memberships = Repo.preload([owner_membership | memberships], :user)
+
+ invitations =
+ from(
+ gi in Teams.GuestInvitation,
+ inner_join: ti in assoc(gi, :team_invitation),
+ where: gi.site_id == ^site.id,
+ select: %Plausible.Auth.Invitation{
+ invitation_id: gi.invitation_id,
+ email: ti.email,
+ role:
+ fragment(
+ """
+ CASE
+ WHEN ? = 'editor' THEN 'admin'
+ ELSE ?
+ END
+ """,
+ gi.role,
+ gi.role
+ )
+ }
+ )
+ |> Repo.all()
+
+ %{memberships: memberships, invitations: invitations}
+ else
+ site
+ |> Repo.preload([:invitations, memberships: :user])
+ |> Map.take([:memberships, :invitations])
+ 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}%"))
diff --git a/lib/plausible/teams/guest_invitation.ex b/lib/plausible/teams/guest_invitation.ex
index 9f0e71002..815a608fa 100644
--- a/lib/plausible/teams/guest_invitation.ex
+++ b/lib/plausible/teams/guest_invitation.ex
@@ -8,6 +8,7 @@ defmodule Plausible.Teams.GuestInvitation do
import Ecto.Changeset
schema "guest_invitations" do
+ field :invitation_id, :string
field :role, Ecto.Enum, values: [:viewer, :editor]
belongs_to :site, Plausible.Site
@@ -17,7 +18,7 @@ defmodule Plausible.Teams.GuestInvitation do
end
def changeset(team_invitation, site, role) do
- %__MODULE__{}
+ %__MODULE__{invitation_id: Nanoid.generate()}
|> cast(%{role: role}, [:role])
|> validate_required(:role)
|> put_assoc(:team_invitation, team_invitation)
diff --git a/lib/plausible/teams/invitations.ex b/lib/plausible/teams/invitations.ex
index ec88523be..92ce7002b 100644
--- a/lib/plausible/teams/invitations.ex
+++ b/lib/plausible/teams/invitations.ex
@@ -63,7 +63,7 @@ defmodule Plausible.Teams.Invitations do
site_invitation.inviter
)
- guest_invitation.team_invitation
+ guest_invitation
|> Ecto.Changeset.change(invitation_id: site_invitation.invitation_id)
|> Repo.update!()
end
diff --git a/lib/plausible/teams/sites.ex b/lib/plausible/teams/sites.ex
index 84c63757a..4d2e4995c 100644
--- a/lib/plausible/teams/sites.ex
+++ b/lib/plausible/teams/sites.ex
@@ -134,7 +134,8 @@ defmodule Plausible.Teams.Sites do
select: %{
site_id: s.id,
entry_type: "site",
- invitation_id: 0,
+ guest_invitation_id: 0,
+ team_invitation_id: 0,
role: tm.role,
transfer_id: 0
}
@@ -147,7 +148,8 @@ defmodule Plausible.Teams.Sites do
select: %{
site_id: s.id,
entry_type: "site",
- invitation_id: 0,
+ guest_invitation_id: 0,
+ team_invitation_id: 0,
role:
fragment(
"""
@@ -184,7 +186,8 @@ defmodule Plausible.Teams.Sites do
select: %{
site_id: s.id,
entry_type: "invitation",
- invitation_id: ti.id,
+ guest_invitation_id: gi.id,
+ team_invitation_id: ti.id,
role:
fragment(
"""
@@ -216,7 +219,8 @@ defmodule Plausible.Teams.Sites do
select: %{
site_id: s.id,
entry_type: "invitation",
- invitation_id: 0,
+ guest_invitation_id: 0,
+ team_invitation_id: 0,
role: "owner",
transfer_id: st.id
}
@@ -234,7 +238,9 @@ defmodule Plausible.Teams.Sites do
left_join: up in Site.UserPreference,
on: up.site_id == s.id and up.user_id == ^user.id,
left_join: ti in Teams.Invitation,
- on: ti.id == u.invitation_id,
+ on: ti.id == u.team_invitation_id,
+ left_join: gi in Teams.GuestInvitation,
+ on: gi.id == u.guest_invitation_id,
left_join: st in Teams.SiteTransfer,
on: st.id == u.transfer_id,
select: %{
@@ -244,10 +250,12 @@ defmodule Plausible.Teams.Sites do
fragment(
"""
CASE
+ WHEN ? IS NOT NULL THEN 'invitation'
WHEN ? IS NOT NULL THEN 'pinned_site'
ELSE ?
END
""",
+ gi.id,
up.pinned_at,
u.entry_type
),
@@ -263,7 +271,7 @@ defmodule Plausible.Teams.Sites do
],
invitations: [
%Plausible.Auth.Invitation{
- invitation_id: coalesce(ti.invitation_id, st.transfer_id),
+ invitation_id: coalesce(gi.invitation_id, st.transfer_id),
email: coalesce(ti.email, st.email),
role: type(u.role, ^@role_type),
site_id: s.id,
diff --git a/lib/plausible_web/controllers/site/membership_controller.ex b/lib/plausible_web/controllers/site/membership_controller.ex
index 7c69ec3db..d17849f77 100644
--- a/lib/plausible_web/controllers/site/membership_controller.ex
+++ b/lib/plausible_web/controllers/site/membership_controller.ex
@@ -151,10 +151,12 @@ defmodule PlausibleWeb.Site.MembershipController do
|> Enum.map(fn {k, v} -> {v, k} end)
|> Enum.into(%{})
- def update_role(conn, %{"id" => id, "new_role" => new_role_str}) do
+ def update_role_by_user(conn, %{"id" => user_id, "new_role" => new_role_str}) do
%{site: site, current_user: current_user, current_user_role: current_user_role} = conn.assigns
- membership = Repo.get!(Membership, id) |> Repo.preload(:user)
+ membership =
+ Membership |> Repo.get_by!(user_id: user_id, site_id: site.id) |> Repo.preload(:user)
+
new_role = Map.fetch!(@role_mappings, new_role_str)
can_grant_role? =
@@ -202,13 +204,13 @@ defmodule PlausibleWeb.Site.MembershipController do
defp can_grant_role_to_other?(:admin, :viewer), do: true
defp can_grant_role_to_other?(_, _), do: false
- def remove_member(conn, %{"id" => id}) do
- site = conn.assigns[:site]
+ def remove_member_by_user(conn, %{"id" => user_id} = _params) do
+ site = conn.assigns.site
site_id = site.id
membership_q =
from m in Membership,
- where: m.id == ^id,
+ where: m.user_id == ^user_id,
where: m.site_id == ^site_id,
inner_join: user in assoc(m, :user),
inner_join: site in assoc(m, :site),
diff --git a/lib/plausible_web/controllers/site_controller.ex b/lib/plausible_web/controllers/site_controller.ex
index 970d8983a..b2f8620f8 100644
--- a/lib/plausible_web/controllers/site_controller.ex
+++ b/lib/plausible_web/controllers/site_controller.ex
@@ -122,13 +122,17 @@ defmodule PlausibleWeb.SiteController do
end
def settings_people(conn, _params) do
- site =
- conn.assigns[:site]
- |> Repo.preload(memberships: :user, invitations: [])
+ current_user = conn.assigns.current_user
+ site = conn.assigns.site
+
+ %{memberships: memberships, invitations: invitations} =
+ Plausible.Teams.Adapter.Read.Sites.list_people(site, current_user)
conn
|> render("settings_people.html",
site: site,
+ memberships: memberships,
+ invitations: invitations,
dogfood_page_path: "/:dashboard/settings/people",
layout: {PlausibleWeb.LayoutView, "site_settings.html"}
)
diff --git a/lib/plausible_web/router.ex b/lib/plausible_web/router.ex
index 6cedfd093..335fe37ca 100644
--- a/lib/plausible_web/router.ex
+++ b/lib/plausible_web/router.ex
@@ -451,8 +451,11 @@ defmodule PlausibleWeb.Router do
get "/sites/:domain/transfer-ownership", Site.MembershipController, :transfer_ownership_form
post "/sites/:domain/transfer-ownership", Site.MembershipController, :transfer_ownership
- put "/sites/:domain/memberships/:id/role/:new_role", Site.MembershipController, :update_role
- delete "/sites/:domain/memberships/:id", Site.MembershipController, :remove_member
+ put "/sites/:domain/memberships/u/:id/role/:new_role",
+ Site.MembershipController,
+ :update_role_by_user
+
+ delete "/sites/:domain/memberships/u/:id", Site.MembershipController, :remove_member_by_user
get "/sites/:domain/weekly-report/unsubscribe", UnsubscribeController, :weekly_report
get "/sites/:domain/monthly-report/unsubscribe", UnsubscribeController, :monthly_report
diff --git a/lib/plausible_web/templates/site/settings_people.html.heex b/lib/plausible_web/templates/site/settings_people.html.heex
index 4fe73ef76..fdb43ddbb 100644
--- a/lib/plausible_web/templates/site/settings_people.html.heex
+++ b/lib/plausible_web/templates/site/settings_people.html.heex
@@ -14,8 +14,8 @@
- <%= for membership <- @site.memberships do %>
- -
+ <%= for membership <- @memberships do %>
+
-
- <%= membership.role |> Atom.to_string() |> String.capitalize() %>
+ <%= membership.role |> to_string() |> String.capitalize() %>