Skip to content
113 changes: 112 additions & 1 deletion extra/lib/plausible_web/controllers/api/external_sites_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,30 @@ defmodule PlausibleWeb.Api.ExternalSitesController do
})
end

def guests_index(conn, params) do
user = conn.assigns.current_user

with {:ok, site_id} <- expect_param_key(params, "site_id"),
{:ok, site} <- get_site(user, site_id, [:owner, :admin, :editor, :viewer]) do
opts = [cursor_fields: [inserted_at: :desc, id: :desc], limit: 100, maximum_limit: 1000]
page = site |> Sites.list_guests_query() |> paginate(params, opts)

json(conn, %{
guests:
Enum.map(page.entries, fn entry ->
Map.take(entry, [:email, :role, :accepted])
end),
meta: pagination_meta(page.metadata)
})
else
{:missing, "site_id"} ->
H.bad_request(conn, "Parameter `site_id` is required to list goals")

{:error, :site_not_found} ->
H.not_found(conn, "Site could not be found")
end
end

def goals_index(conn, params) do
user = conn.assigns.current_user

Expand Down Expand Up @@ -138,6 +162,81 @@ defmodule PlausibleWeb.Api.ExternalSitesController do
end
end

def find_or_create_guest(conn, params) do
with {:ok, site_id} <- expect_param_key(params, "site_id"),
{:ok, email} <- expect_param_key(params, "email"),
{:ok, role} <- expect_param_key(params, "role", ["viewer", "editor"]),
{:ok, site} <- get_site(conn.assigns.current_user, site_id, [:owner, :admin]) do
existing = Repo.one(Sites.list_guests_query(site, email: email))

if existing do
json(conn, %{
role: existing.role,
email: existing.email,
accepted: existing.accepted
})
else
case Plausible.Site.Memberships.CreateInvitation.create_invitation(
site,
conn.assigns.current_user,
email,
role
) do
{:ok, invitation} ->
json(conn, %{
role: invitation.role,
email: invitation.team_invitation.email,
accepted: false
})
end
end
else
{:error, :site_not_found} ->
H.not_found(conn, "Site could not be found")

{:missing, "role"} ->
H.bad_request(
conn,
"Parameter `role` is required to create guest. Possible values: `viewer` or `editor`"
)

{:missing, param} ->
H.bad_request(conn, "Parameter `#{param}` is required to create guest")
end
end

def delete_guest(conn, params) do
with {:ok, site_id} <- expect_param_key(params, "site_id"),
{:ok, email} <- expect_param_key(params, "email"),
{:ok, site} <- get_site(conn.assigns.current_user, site_id, [:owner, :admin]) do
existing = Repo.one(Sites.list_guests_query(site, email: email))

case existing do
%{accepted: false, id: id} ->
with guest_invitation when not is_nil(guest_invitation) <-
Repo.get(Teams.GuestInvitation, id) do
Teams.Invitations.remove_guest_invitation(guest_invitation)
end

%{accepted: true, email: email} ->
with %{} = user <- Repo.get_by(Plausible.Auth.User, email: email) do
Teams.Memberships.remove(site, user)
end

_ ->
:ignore
end

json(conn, %{"deleted" => true})
else
{:error, :site_not_found} ->
H.not_found(conn, "Site could not be found")

{:missing, param} ->
H.bad_request(conn, "Parameter `#{param}` is required to delete a guest")
end
end

def find_or_create_shared_link(conn, params) do
with {:ok, site_id} <- expect_param_key(params, "site_id"),
{:ok, link_name} <- expect_param_key(params, "name"),
Expand Down Expand Up @@ -235,10 +334,22 @@ defmodule PlausibleWeb.Api.ExternalSitesController do
%{"error" => error_msg}
end

defp expect_param_key(params, key) do
defp expect_param_key(params, key, inclusion \\ [])

defp expect_param_key(params, key, []) do
case Map.fetch(params, key) do
:error -> {:missing, key}
res -> res
end
end

defp expect_param_key(params, key, inclusion) do
case expect_param_key(params, key, []) do
{:ok, value} = ok ->
if value in inclusion, do: ok, else: {:missing, key}

_ ->
{:missing, key}
end
end
end
61 changes: 61 additions & 0 deletions lib/plausible/sites.ex
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,67 @@ defmodule Plausible.Sites do
%{memberships: memberships, invitations: site_transfers ++ invitations}
end

@spec list_guests_query(Site.t(), Keyword.t()) :: Ecto.Query.t()
def list_guests_query(site, opts \\ []) do
guest_memberships =
from(
gm in Teams.GuestMembership,
inner_join: tm in assoc(gm, :team_membership),
inner_join: u in assoc(tm, :user),
as: :user,
where: gm.site_id == ^site.id,
select: %{
id: gm.id,
inserted_at: gm.inserted_at,
email: u.email,
role: gm.role,
accepted: true
}
)

guest_memberships =
if email = opts[:email] do
guest_memberships |> where([user: u], u.email == ^email)
else
guest_memberships
end

guest_invitations =
from(
gi in Teams.GuestInvitation,
inner_join: ti in assoc(gi, :team_invitation),
as: :team_invitation,
where: gi.site_id == ^site.id,
select: %{
id: gi.id,
inserted_at: gi.inserted_at,
email: ti.email,
role: gi.role,
accepted: false
}
)

guest_invitations =
if email = opts[:email] do
guest_invitations |> where([team_invitation: ti], ti.email == ^email)
else
guest_invitations
end

guests = union_all(guest_memberships, ^guest_invitations)

from(g in subquery(guests),
select: %{
id: g.id,
inserted_at: g.inserted_at,
email: g.email,
role: g.role,
accepted: g.accepted
},
order_by: [desc: g.inserted_at, desc: g.id]
)
end

@spec for_user_query(Auth.User.t(), Teams.Team.t() | nil) :: Ecto.Query.t()
def for_user_query(user, team \\ nil) do
query =
Expand Down
6 changes: 6 additions & 0 deletions lib/plausible_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ defmodule PlausibleWeb.Router do

get "/", ExternalSitesController, :index
get "/goals", ExternalSitesController, :goals_index
get "/guests", ExternalSitesController, :guests_index
get "/:site_id", ExternalSitesController, :get_site
end

Expand All @@ -275,8 +276,13 @@ defmodule PlausibleWeb.Router do

post "/", ExternalSitesController, :create_site
put "/shared-links", ExternalSitesController, :find_or_create_shared_link

put "/goals", ExternalSitesController, :find_or_create_goal
delete "/goals/:goal_id", ExternalSitesController, :delete_goal

put "/guests", ExternalSitesController, :find_or_create_guest
delete "/guests/:email", ExternalSitesController, :delete_guest

put "/:site_id", ExternalSitesController, :update_site
delete "/:site_id", ExternalSitesController, :delete_site
end
Expand Down
Loading
Loading