Skip to content
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions lib/nerves_hub/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -442,8 +442,12 @@ defmodule NervesHub.Accounts do
| {:error, Changeset.t()}
def add_or_invite_to_org(%{"email" => email} = params, org) do
case get_user_by_email(email) do
{:error, :not_found} -> invite(params, org)
{:ok, user} -> add_org_user(org, user, %{role: params["role"]})
{:error, :not_found} ->
invite(params, org)

{:ok, _user} ->
invite(params, org)
# add_org_user(org, user, %{role: params["role"]})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this can be simplified to always invite, and not having to search for the user

end
end

Expand Down Expand Up @@ -520,6 +524,31 @@ defmodule NervesHub.Accounts do
end)
end

@spec accept_invite(Invite.t(), Org.t()) ::
{:ok, OrgUser.t()} | {:error, Ecto.Changeset.t()}
def accept_invite(invite, org) do
Repo.transaction(fn ->
with {:ok, user} <- get_user_by_email(invite.email),
{:ok, user} <- add_org_user(org, user, %{role: invite.role}),
{:ok, _invite} <- set_invite_accepted(invite) do
# Repo.transaction will wrap this in an {:ok, user}
user
else
{:error, error} -> Repo.rollback(error)
end
end)
end

@spec user_invite_recipient?(Invite.t(), User.t()) ::
{:ok, Invite.t()} | {:error, :invite_not_for_user}
def user_invite_recipient?(invite, user) do
if invite.email == user.email do
{:ok, invite}
else
{:error, :invite_not_for_user}
end
end

@spec update_user(User.t(), map) ::
{:ok, User.t()}
| {:error, Changeset.t()}
Expand Down
114 changes: 107 additions & 7 deletions lib/nerves_hub_web/controllers/account_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,43 @@ defmodule NervesHubWeb.AccountController do
def invite(conn, %{"token" => token} = _) do
with {:ok, invite} <- Accounts.get_valid_invite(token),
{:ok, org} <- Accounts.get_org(invite.org_id) do
render(
conn,
"invite.html",
changeset: %Changeset{data: invite},
org: org,
token: token
)
# QUESTION: Should this be here raw or in a method somewhere else?
case Map.has_key?(conn.assigns, :user) && !is_nil(conn.assigns.user) do
true ->
if invite.email == conn.assigns.user.email do
render(
conn,
# QUESTION: Should this be a separate template or the same one with conditional rendering?
"invite_existing.html",
changeset: %Changeset{data: invite},
org: org,
token: token
)
else
conn
|> put_flash(:error, "Invite not intended for the current user")
|> redirect(to: "/")
end

false ->
case Accounts.get_user_by_email(invite.email) do
# Invites for existing users
{:ok, _recipient} ->
conn
|> put_flash(:error, "You must be logged in to accept this invite")
|> redirect(to: "/login")

# Invites for new users
{:error, :not_found} ->
render(
conn,
"invite.html",
changeset: %Changeset{data: invite},
org: org,
token: token
)
end
end
else
_ ->
conn
Expand Down Expand Up @@ -90,6 +120,38 @@ defmodule NervesHubWeb.AccountController do
end
end

def accept_invite_existing(conn, %{"token" => token} = _) do
# QUESTION rep: Should this be here raw or in a method somewhere else?
case Map.has_key?(conn.assigns, :user) && !is_nil(conn.assigns.user) do
true ->
with {:ok, invite} <- Accounts.get_valid_invite(token),
{:ok, org} <- Accounts.get_org(invite.org_id),
{:ok, _} <- Accounts.user_invite_recipient?(invite, conn.assigns.user) do
_accept_invite_existing(conn, token, invite, org)
else
{:error, :invite_not_found} ->
conn
|> put_flash(:error, "Invalid or expired invite")
|> redirect(to: "/")

{:error, :org_not_found} ->
conn
|> put_flash(:error, "Invalid org")
|> redirect(to: "/")

{:error, :invite_not_for_user} ->
conn
|> put_flash(:error, "Invite not intended for the current user")
|> redirect(to: "/")
end

false ->
conn
|> put_flash(:error, "You must be logged in to accept this invite")
|> redirect(to: "/")
end
end

defp _accept_invite(conn, token, clean_params, invite, org) do
with {:ok, new_org_user} <- Accounts.create_user_from_invite(invite, org, clean_params) do
# Now let everyone in the organization - except the new guy -
Expand Down Expand Up @@ -127,4 +189,42 @@ defmodule NervesHubWeb.AccountController do
)
end
end

defp _accept_invite_existing(conn, token, invite, org) do
with {:ok, new_org_user} <- Accounts.accept_invite(invite, org) do
# Now let everyone in the organization - except the new guy -
# know about this new user.

# TODO: Fix this - We don't have the instigating user in the conn
# anymore, and the new user is not always the instigator.
instigator =
case conn.assigns do
%{user: %{username: username}} -> username
_ -> nil
end

email =
SwooshEmail.tell_org_user_added(
org,
Accounts.get_org_users(org),
instigator,
new_org_user.user
)

SwooshMailer.deliver(email)

conn
|> put_flash(:info, "Organization successfully joined")
|> redirect(to: "/")
else
{:error, %Changeset{} = changeset} ->
render(
conn,
"invite_existing.html",
changeset: changeset,
org: org,
token: token
)
end
end
end
1 change: 1 addition & 0 deletions lib/nerves_hub_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ defmodule NervesHubWeb.Router do

get("/invite/:token", AccountController, :invite)
post("/invite/:token", AccountController, :accept_invite)
post("/invite_existing/:token", AccountController, :accept_invite_existing)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should all be possible without another route.

end

scope "/", NervesHubWeb do
Expand Down
12 changes: 12 additions & 0 deletions lib/nerves_hub_web/templates/account/invite_existing.html.heex
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<div class="form-page-wrapper">
<h2 class="form-title">
Organization Invitation
</h2>

<h5 class="mt-2"><%= gettext("You have been invited to join to the %{organization_name} organization", organization_name: @org.name) %></h5>

<%= form_for @changeset, Routes.account_path(@conn, :accept_invite_existing, @token), [method: "post", class: "form-page"], fn f -> %>
<div class="has-error"><%= error_tag(f, :email) %></div>
<%= submit("Accept Invitation", class: "btn btn-primary btn-lg w-100") %>
<% end %>
</div>