From 72ebe44145bfea89c7a58b29100c5c1a7cbb9426 Mon Sep 17 00:00:00 2001 From: Adrian Gruntkowski Date: Wed, 8 Jan 2025 14:45:27 +0100 Subject: [PATCH] Implement accepting team invite within the existing flow --- .../site/memberships/accept_invitation.ex | 13 ++++++- lib/plausible/teams/invitations.ex | 36 ++++++++++++++----- .../teams/invitations/accept_team_invite.ex | 9 ----- lib/plausible/teams/users.ex | 10 ++++++ .../controllers/invitation_controller.ex | 5 +++ lib/plausible_web/email.ex | 19 ++++++++-- ...ex => guest_invitation_accepted.html.heex} | 0 .../email/team_invitation_accepted.html.heex | 2 ++ 8 files changed, 72 insertions(+), 22 deletions(-) delete mode 100644 lib/plausible/teams/invitations/accept_team_invite.ex rename lib/plausible_web/templates/email/{invitation_accepted.html.heex => guest_invitation_accepted.html.heex} (100%) create mode 100644 lib/plausible_web/templates/email/team_invitation_accepted.html.heex diff --git a/lib/plausible/site/memberships/accept_invitation.ex b/lib/plausible/site/memberships/accept_invitation.ex index 1d4618becdd6..9733e9161bbb 100644 --- a/lib/plausible/site/memberships/accept_invitation.ex +++ b/lib/plausible/site/memberships/accept_invitation.ex @@ -28,6 +28,7 @@ defmodule Plausible.Site.Memberships.AcceptInvitation do @type accept_error() :: :invitation_not_found + | :already_other_team_member | Billing.Quota.Limits.over_limits_error() | Ecto.Changeset.t() | :no_plan @@ -111,6 +112,16 @@ defmodule Plausible.Site.Memberships.AcceptInvitation do end defp do_accept_team_invitation(team_invitation, user) do - Teams.Invitations.AcceptTeamInvite.accept(team_invitation, user) + with :ok <- ensure_no_team_membership(user) do + Teams.Invitations.accept_team_invitation(team_invitation, user) + end + end + + defp ensure_no_team_membership(user) do + if Teams.Users.team_member?(user) do + {:error, :already_other_team_member} + else + :ok + end end end diff --git a/lib/plausible/teams/invitations.ex b/lib/plausible/teams/invitations.ex index 36c2cad889ae..01c4cdeaddd7 100644 --- a/lib/plausible/teams/invitations.ex +++ b/lib/plausible/teams/invitations.ex @@ -192,11 +192,14 @@ defmodule Plausible.Teams.Invitations do now = NaiveDateTime.utc_now(:second) - with {:ok, team_membership} <- - do_accept(team_invitation, user, now, guest_invitations: [guest_invitation]) do - prune_guest_invitations(team_invitation.team) - {:ok, team_membership} - end + do_accept(team_invitation, user, now, guest_invitations: [guest_invitation]) + end + + def accept_team_invitation(team_invitation, user) do + team_invitation = Repo.preload(team_invitation, [:team, :inviter]) + now = NaiveDateTime.utc_now(:second) + + do_accept(team_invitation, user, now, guest_invitations: []) end @doc false @@ -264,6 +267,10 @@ defmodule Plausible.Teams.Invitations do Repo.delete_all(from gi in Teams.GuestInvitation, where: gi.id in ^guest_invitation_ids) prune_guest_invitations(team_invitation.team) + # Prune guest memberships if any exist when team membership role + # is other than guest + maybe_prune_guest_memberships(team_membership) + if send_email? do send_invitation_accepted_email(team_invitation, guest_invitations) end @@ -275,6 +282,16 @@ defmodule Plausible.Teams.Invitations do end) end + defp maybe_prune_guest_memberships(%Teams.Membership{role: :guest}), do: :ok + + defp maybe_prune_guest_memberships(%Teams.Membership{} = team_membership) do + team_membership + |> Ecto.assoc(:guest_memberships) + |> Repo.delete_all() + + :ok + end + defp transfer_site_ownership(site, team, now) do site = Repo.preload(site, [ @@ -643,14 +660,15 @@ defmodule Plausible.Teams.Invitations do end) end - defp send_invitation_accepted_email(_team_invitation, []) do - # NOOP for now - :ok + defp send_invitation_accepted_email(team_invitation, []) do + team_invitation.inviter.email + |> PlausibleWeb.Email.team_invitation_accepted(team_invitation.email, team_invitation.team) + |> Plausible.Mailer.send() end defp send_invitation_accepted_email(team_invitation, [guest_invitation | _]) do team_invitation.inviter.email - |> PlausibleWeb.Email.invitation_accepted(team_invitation.email, guest_invitation.site) + |> PlausibleWeb.Email.guest_invitation_accepted(team_invitation.email, guest_invitation.site) |> Plausible.Mailer.send() end diff --git a/lib/plausible/teams/invitations/accept_team_invite.ex b/lib/plausible/teams/invitations/accept_team_invite.ex deleted file mode 100644 index 3a07937e90ea..000000000000 --- a/lib/plausible/teams/invitations/accept_team_invite.ex +++ /dev/null @@ -1,9 +0,0 @@ -defmodule Plausible.Teams.Invitations.AcceptTeamInvite do - @moduledoc """ - Service for accepting a team invite. - """ - - def accept(_team_invitation, _user) do - {:ok, nil} - end -end diff --git a/lib/plausible/teams/users.ex b/lib/plausible/teams/users.ex index e870127268cb..fd0509919f5b 100644 --- a/lib/plausible/teams/users.ex +++ b/lib/plausible/teams/users.ex @@ -8,6 +8,16 @@ defmodule Plausible.Teams.Users do alias Plausible.Repo alias Plausible.Teams + def team_member?(user) do + Repo.exists?( + from( + tm in Teams.Membership, + where: tm.user_id == ^user.id, + where: tm.role != :guest + ) + ) + end + def has_sites?(user, opts \\ []) do include_pending? = Keyword.get(opts, :include_pending?, false) diff --git a/lib/plausible_web/controllers/invitation_controller.ex b/lib/plausible_web/controllers/invitation_controller.ex index 4fc675e49bb8..34a45b05a500 100644 --- a/lib/plausible_web/controllers/invitation_controller.ex +++ b/lib/plausible_web/controllers/invitation_controller.ex @@ -27,6 +27,11 @@ defmodule PlausibleWeb.InvitationController do |> put_flash(:error, "Invitation missing or already accepted") |> redirect(to: "/sites") + {:error, :already_other_team_member} -> + conn + |> put_flash(:error, "You already are a team member in another team") + |> redirect(to: "/sites") + {:error, :no_plan} -> conn |> put_flash(:error, "No existing subscription") diff --git a/lib/plausible_web/email.ex b/lib/plausible_web/email.ex index aa5a9ad9f843..260c5f23cc08 100644 --- a/lib/plausible_web/email.ex +++ b/lib/plausible_web/email.ex @@ -299,19 +299,32 @@ defmodule PlausibleWeb.Email do ) end - def invitation_accepted(inviter_email, invitee_email, site) do + def guest_invitation_accepted(inviter_email, invitee_email, site) do priority_email() |> to(inviter_email) - |> tag("invitation-accepted") + |> tag("guest-invitation-accepted") |> subject( "[#{Plausible.product_name()}] #{invitee_email} accepted your invitation to #{site.domain}" ) - |> render("invitation_accepted.html", + |> render("guest_invitation_accepted.html", invitee_email: invitee_email, site: site ) end + def team_invitation_accepted(inviter_email, invitee_email, team) do + priority_email() + |> to(inviter_email) + |> tag("team-invitation-accepted") + |> subject( + "[#{Plausible.product_name()}] #{invitee_email} accepted your invitation to \"#{team.name}\" team" + ) + |> render("team_invitation_accepted.html", + invitee_email: invitee_email, + team: team + ) + end + def guest_invitation_rejected(guest_invitation) do priority_email() |> to(guest_invitation.team_invitation.inviter.email) diff --git a/lib/plausible_web/templates/email/invitation_accepted.html.heex b/lib/plausible_web/templates/email/guest_invitation_accepted.html.heex similarity index 100% rename from lib/plausible_web/templates/email/invitation_accepted.html.heex rename to lib/plausible_web/templates/email/guest_invitation_accepted.html.heex diff --git a/lib/plausible_web/templates/email/team_invitation_accepted.html.heex b/lib/plausible_web/templates/email/team_invitation_accepted.html.heex new file mode 100644 index 000000000000..fe4504d844af --- /dev/null +++ b/lib/plausible_web/templates/email/team_invitation_accepted.html.heex @@ -0,0 +1,2 @@ +<%= @invitee_email %> has accepted your invitation to "<%= @team.name %>" team. +Click here to view team settings.