diff --git a/lib/nerves_hub/accounts.ex b/lib/nerves_hub/accounts.ex index d2b088a18..e781c8033 100644 --- a/lib/nerves_hub/accounts.ex +++ b/lib/nerves_hub/accounts.ex @@ -424,8 +424,12 @@ defmodule NervesHub.Accounts do | {:error, Changeset.t()} def add_or_invite_to_org(%{"email" => email} = params, org, invited_by) do case get_user_by_email(email) do - {:error, :not_found} -> invite(params, org, invited_by) - {:ok, user} -> add_org_user(org, user, %{role: params["role"]}) + {:error, :not_found} -> + invite(params, org, invited_by) + + {:ok, _user} -> + invite(params, org, invited_by) + # add_org_user(org, user, %{role: params["role"]}) end end @@ -473,6 +477,14 @@ defmodule NervesHub.Accounts do |> Repo.all() end + @spec get_invites_for_user(User.t()) :: [Invite.t()] + def get_invites_for_user(user) do + Invite + |> where([i], i.email == ^user.email) + |> where([i], i.accepted == false) + |> Repo.all() + end + def delete_invite(org, token) do query = Invite @@ -513,6 +525,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()} diff --git a/lib/nerves_hub_web/controllers/account_controller.ex b/lib/nerves_hub_web/controllers/account_controller.ex index c776de65d..59b91eeb9 100644 --- a/lib/nerves_hub_web/controllers/account_controller.ex +++ b/lib/nerves_hub_web/controllers/account_controller.ex @@ -6,19 +6,32 @@ defmodule NervesHubWeb.AccountController do alias NervesHub.Accounts.{User, SwooshEmail} alias NervesHub.SwooshMailer + import Phoenix.HTML.Link + plug(:registrations_allowed when action in [:new, :create]) def new(conn, _params) do render(conn, "new.html", changeset: Ecto.Changeset.change(%User{})) end - def create(conn, %{"user" => user_params} = _) do - case Accounts.create_user(user_params) do - {:ok, new_user} -> - new_user - |> SwooshEmail.welcome_user() - |> SwooshMailer.deliver() + def delete(conn, %{"user_name" => username}) do + with {:ok, user} <- Accounts.get_user_by_username(username), + {:ok, _} <- Accounts.remove_account(user.id) do + conn + |> put_flash(:info, "Success") + |> redirect(to: "/login") + end + end + + def update(conn, params) do + cleaned = + params["user"] + |> whitelist([:current_password, :password, :username, :email, :orgs]) + conn.assigns.user + |> Accounts.update_user(cleaned) + |> case do + {:ok, _user} -> conn |> put_flash(:info, "Account successfully created, login below") |> redirect(to: "/login") @@ -31,13 +44,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 @@ -47,9 +90,11 @@ defmodule NervesHubWeb.AccountController do end def accept_invite(conn, %{"user" => user_params, "token" => token} = _) do + clean_params = whitelist(user_params, [:password, :username]) + with {:ok, invite} <- Accounts.get_valid_invite(token), {:ok, org} <- Accounts.get_org(invite.org_id) do - _accept_invite(conn, token, user_params, invite, org) + _accept_invite(conn, token, clean_params, invite, org) else {:error, :invite_not_found} -> conn @@ -92,6 +137,35 @@ defmodule NervesHubWeb.AccountController do end end + def maybe_show_invites(conn) do + case Map.has_key?(conn.assigns, :user) && !is_nil(conn.assigns.user) do + true -> + case conn.assigns.user + |> Accounts.get_invites_for_user() do + [] -> + conn + + invites -> + conn + |> put_flash( + :info, + [ + "You have " <> + (length(invites) |> Integer.to_string()) <> + " pending invite" <> + if(length(invites) > 1, do: "s", else: "") <> " to organizations. ", + link("Click here to view pending invites.", + to: "/org/" <> conn.assigns.user.username <> "/invites" + ) + ] + ) + end + + false -> + conn + end + end + defp registrations_allowed(conn, _options) do if Application.get_env(:nerves_hub, :open_for_registrations) do conn diff --git a/lib/nerves_hub_web/controllers/home_controller.ex b/lib/nerves_hub_web/controllers/home_controller.ex index 44d7f1656..68899eff4 100644 --- a/lib/nerves_hub_web/controllers/home_controller.ex +++ b/lib/nerves_hub_web/controllers/home_controller.ex @@ -1,7 +1,11 @@ defmodule NervesHubWeb.HomeController do use NervesHubWeb, :controller + alias NervesHubWeb.AccountController + def index(conn, _params) do - redirect(conn, to: ~p"/orgs") + conn + |> AccountController.maybe_show_invites() + |> redirect(to: ~p"/orgs") end end diff --git a/lib/nerves_hub_web/router.ex b/lib/nerves_hub_web/router.ex index 26827d670..da3e2fb48 100644 --- a/lib/nerves_hub_web/router.ex +++ b/lib/nerves_hub_web/router.ex @@ -200,6 +200,12 @@ defmodule NervesHubWeb.Router do get("/invite/:token", AccountController, :invite) post("/invite/:token", AccountController, :accept_invite) + + scope "/invites/:user_name" do + pipe_through([:logged_in]) + + get("/", AccountController, :invites) + end end scope "/", NervesHubWeb do diff --git a/lib/nerves_hub_web/templates/account/invite_existing.html.heex b/lib/nerves_hub_web/templates/account/invite_existing.html.heex new file mode 100644 index 000000000..b2afec9b6 --- /dev/null +++ b/lib/nerves_hub_web/templates/account/invite_existing.html.heex @@ -0,0 +1,12 @@ +
+

+ Organization Invitation +

+ +
<%= gettext("You have been invited to join to the %{organization_name} organization", organization_name: @org.name) %>
+ + <%= form_for @changeset, Routes.account_path(@conn, :accept_invite, @token), [method: "post", class: "form-page"], fn f -> %> +
<%= error_tag(f, :email) %>
+ <%= submit("Accept Invitation", class: "btn btn-primary btn-lg w-100") %> + <% end %> +