diff --git a/lib/safira/accounts.ex b/lib/safira/accounts.ex
index 19644a938..32cdce1c4 100644
--- a/lib/safira/accounts.ex
+++ b/lib/safira/accounts.ex
@@ -182,15 +182,16 @@ defmodule Safira.Accounts do
|> Flop.validate_and_run(params, for: User)
end
- def update_staff(%Staff{} = staff, attrs) do
- Staff.changeset(staff, attrs)
+ def update_user(%User{} = user, attrs) do
+ user
+ |> User.changeset(attrs)
|> Repo.update()
end
@doc """
Changes a staff
"""
- def change_staff(%Staff{} = staff, attrs) do
+ def change_staff(%Staff{} = staff, attrs \\ %{}) do
Staff.changeset(staff, attrs)
end
@@ -289,9 +290,34 @@ defmodule Safira.Accounts do
"""
def register_staff_user(attrs) do
- %User{}
- |> User.registration_changeset(attrs |> Map.put(:type, :staff))
- |> Repo.insert()
+ attrs = attrs |> Map.put("type", :staff)
+
+ Ecto.Multi.new()
+ |> Ecto.Multi.insert(
+ :user,
+ User.registration_changeset(
+ %User{},
+ Map.delete(attrs, "staff"),
+ hash_password: true,
+ validate_email: true
+ )
+ )
+ |> Ecto.Multi.insert(
+ :staff,
+ fn %{user: user} ->
+ staff_attrs =
+ attrs
+ |> Map.get("staff")
+ |> Map.put("user_id", user.id)
+
+ Staff.changeset(%Staff{}, staff_attrs)
+ end
+ )
+ |> Ecto.Multi.update(
+ :new_user,
+ fn %{user: user} -> User.confirm_changeset(user) end
+ )
+ |> Repo.transaction()
end
@doc """
diff --git a/lib/safira/accounts/staff.ex b/lib/safira/accounts/staff.ex
index ed603b4a2..be2850e32 100644
--- a/lib/safira/accounts/staff.ex
+++ b/lib/safira/accounts/staff.ex
@@ -4,11 +4,13 @@ defmodule Safira.Accounts.Staff do
"""
use Safira.Schema
+ alias Safira.Accounts.User
+
@required_fields ~w(user_id role_id)a
@optional_fields ~w()a
schema "staffs" do
- belongs_to :user, Safira.Accounts.User
+ belongs_to :user, User
belongs_to :role, Safira.Accounts.Role
timestamps(type: :utc_datetime)
@@ -17,7 +19,6 @@ defmodule Safira.Accounts.Staff do
def changeset(staff, attrs) do
staff
|> cast(attrs, @required_fields ++ @optional_fields)
- |> cast_assoc(:user)
|> validate_required(@required_fields)
end
end
diff --git a/lib/safira/accounts/user.ex b/lib/safira/accounts/user.ex
index 253754beb..3b03ea3b5 100644
--- a/lib/safira/accounts/user.ex
+++ b/lib/safira/accounts/user.ex
@@ -44,7 +44,7 @@ defmodule Safira.Accounts.User do
field :allows_marketing, :boolean, default: false
has_one :attendee, Attendee, on_delete: :delete_all
- has_one :staff, Staff, on_delete: :delete_all
+ has_one :staff, Staff, on_delete: :delete_all, on_replace: :update
has_one :company, Company, on_delete: :delete_all
timestamps(type: :utc_datetime)
@@ -81,6 +81,18 @@ defmodule Safira.Accounts.User do
|> validate_handle()
|> validate_password(opts)
|> cast_assoc(:attendee, with: &Attendee.changeset/2)
+ |> cast_assoc(:staff, with: &Staff.changeset/2)
+ end
+
+ def changeset(user, attrs, opts \\ []) do
+ user
+ |> cast(attrs, @required_fields ++ @optional_fields)
+ |> validate_required(@required_fields |> Enum.reject(&(&1 in [:email, :password, :handle])))
+ |> validate_email(opts)
+ |> validate_handle()
+ |> validate_length(:password, min: 12, max: 72)
+ |> cast_assoc(:attendee, with: &Attendee.changeset/2)
+ |> cast_assoc(:staff, with: &Staff.changeset/2)
end
@doc """
diff --git a/lib/safira/release.ex b/lib/safira/release.ex
index a9c1a0bc7..688bf1465 100644
--- a/lib/safira/release.ex
+++ b/lib/safira/release.ex
@@ -41,19 +41,13 @@ defmodule Safira.Release do
end
# Create user
- case Safira.Accounts.register_staff_user(%{
- name: name,
- email: email,
- password: password,
- handle: handle
- }) do
- {:ok, user} ->
- # Assign role to user
- Safira.Accounts.create_staff(%{user_id: user.id, role_id: role.id})
-
- {:error, changeset} ->
- {:error, changeset}
- end
+ Safira.Accounts.register_staff_user(%{
+ name: name,
+ email: email,
+ password: password,
+ handle: handle,
+ role_id: role.id
+ })
end
defp repos do
diff --git a/lib/safira_web/live/backoffice/staff_live/form_component.ex b/lib/safira_web/live/backoffice/staff_live/form_component.ex
index d806ce51b..627760a27 100644
--- a/lib/safira_web/live/backoffice/staff_live/form_component.ex
+++ b/lib/safira_web/live/backoffice/staff_live/form_component.ex
@@ -3,7 +3,8 @@ defmodule SafiraWeb.Backoffice.StaffLive.FormComponent do
import SafiraWeb.Components.Forms
alias Safira.Accounts
- alias Safira.Roles
+ alias Safira.Accounts.User
+ alias Safira.Repo
@impl true
def render(assigns) do
@@ -19,7 +20,18 @@ defmodule SafiraWeb.Backoffice.StaffLive.FormComponent do
>
- <.field field={@form[:role_id]} type="select" label="Role" options={@roles} required />
+ <.field field={@form[:name]} type="text" label="Name" required />
+ <.field field={@form[:email]} type="email" label="Email" required />
+ <.field
+ field={@form[:password]}
+ type="password"
+ label="Password"
+ required={@action == :new}
+ />
+ <.field field={@form[:handle]} type="text" label="Handle" required />
+ <.inputs_for :let={u} field={@form[:staff]}>
+ <.field field={u[:role_id]} type="select" label="Role" options={@roles} required />
+
<:actions>
@@ -33,41 +45,51 @@ defmodule SafiraWeb.Backoffice.StaffLive.FormComponent do
@impl true
def mount(socket) do
- roles =
- Roles.list_roles()
- |> Enum.map(&{&1.name, &1.id})
-
- {:ok,
- socket
- |> assign(:roles, roles)}
+ {:ok, socket}
end
@impl true
- def update(%{id: id} = assigns, socket) do
- staff = Accounts.get_staff!(id)
- change_staff = Accounts.change_staff(staff, %{})
- form = to_form(change_staff, as: "staff")
+ def update(%{user: user} = assigns, socket) do
+ user = user |> Repo.preload(:staff)
+ user_changeset = Accounts.change_user_registration(user)
{:ok,
socket
|> assign(assigns)
- |> assign(:staff, staff)
- |> assign(:form, form)}
+ |> assign(:user, user)
+ |> assign(:form, to_form(user_changeset))}
end
@impl true
- def handle_event("validate", %{"staff" => staff_params}, socket) do
- {:noreply,
- socket
- |> assign_new(:form, fn ->
- to_form(Accounts.change_staff(socket.assigns.staff, staff_params))
- end)}
+ def handle_event("validate", %{"user" => user_params}, socket) do
+ changeset =
+ Accounts.change_user_registration(%User{}, user_params)
+
+ {:noreply, assign(socket, form: to_form(changeset, action: :validate))}
end
- def handle_event("save", %{"staff" => staff_params}, socket) do
- staff = socket.assigns.staff
+ def handle_event("save", %{"user" => user_params}, socket) do
+ save_staff(socket, socket.assigns.action, user_params)
+ end
- case Accounts.update_staff(staff, staff_params) do
+ defp save_staff(socket, :new, user_params) do
+ case Accounts.register_staff_user(user_params) do
+ {:ok, _staff} ->
+ {:noreply,
+ socket
+ |> put_flash(:info, "Staff created successfully")
+ |> push_patch(to: socket.assigns.patch)}
+
+ {:error, _, %Ecto.Changeset{} = changeset, _} ->
+ {:noreply,
+ socket
+ |> put_flash(:error, gettext("Something went wrong while creating the staff."))
+ |> assign(:form, to_form(changeset, action: :validate))}
+ end
+ end
+
+ defp save_staff(socket, :edit, user_params) do
+ case Accounts.update_user(socket.assigns.user, user_params) do
{:ok, _staff} ->
{:noreply,
socket
@@ -78,8 +100,7 @@ defmodule SafiraWeb.Backoffice.StaffLive.FormComponent do
{:noreply,
socket
|> put_flash(:error, gettext("Something went wrong when updating the staff."))
- |> assign(:form, to_form(changeset))
- |> push_patch(to: socket.assigns.patch)}
+ |> assign(:form, to_form(changeset))}
end
end
end
diff --git a/lib/safira_web/live/backoffice/staff_live/index.ex b/lib/safira_web/live/backoffice/staff_live/index.ex
index 7dfd44769..618096889 100644
--- a/lib/safira_web/live/backoffice/staff_live/index.ex
+++ b/lib/safira_web/live/backoffice/staff_live/index.ex
@@ -7,8 +7,13 @@ defmodule SafiraWeb.Backoffice.StaffLive.Index do
import SafiraWeb.Components.{Table, TableSearch}
+ alias Safira.Accounts.User
+ alias Safira.Repo
+ alias Safira.Roles
+
on_mount {SafiraWeb.StaffRoles,
index: %{"staffs" => ["show"]},
+ new: %{"staffs" => ["edit"]},
edit: %{"staffs" => ["edit"]},
roles: %{"staffs" => ["roles_edit"]},
roles_edit: %{"staffs" => ["roles_edit"]},
@@ -32,7 +37,8 @@ defmodule SafiraWeb.Backoffice.StaffLive.Index do
|> assign(:meta, meta)
|> assign(:params, params)
|> assign(:current_page, :staffs)
- |> assign(:staffs, staffs)}
+ |> assign(:staffs, staffs)
+ |> apply_action(socket.assigns.live_action, params)}
{:error, _} ->
{:noreply, socket}
@@ -67,4 +73,38 @@ defmodule SafiraWeb.Backoffice.StaffLive.Index do
_ -> Map.put(staff, :is_online, value)
end
end
+
+ defp apply_action(socket, :index, _params) do
+ socket
+ |> assign(:page_title, "Listing Staffs")
+ end
+
+ defp apply_action(socket, :new, _params) do
+ roles =
+ Roles.list_roles()
+ |> Enum.map(&{&1.name, &1.id})
+
+ socket
+ |> assign(:page_title, "New Staff")
+ |> assign(:user, %User{})
+ |> assign(:roles, roles)
+ end
+
+ defp apply_action(socket, :edit, %{"id" => id}) do
+ roles =
+ Roles.list_roles()
+ |> Enum.map(&{&1.name, &1.id})
+
+ staff = Accounts.get_staff!(id) |> Repo.preload(:user)
+ user = if staff.user, do: staff.user, else: %User{}
+
+ socket
+ |> assign(:page_title, "Edit Staff")
+ |> assign(:user, user)
+ |> assign(:roles, roles)
+ end
+
+ defp apply_action(socket, _live_action, _params) do
+ socket
+ end
end
diff --git a/lib/safira_web/live/backoffice/staff_live/index.html.heex b/lib/safira_web/live/backoffice/staff_live/index.html.heex
index 110864353..62f488211 100644
--- a/lib/safira_web/live/backoffice/staff_live/index.html.heex
+++ b/lib/safira_web/live/backoffice/staff_live/index.html.heex
@@ -1,14 +1,6 @@
-<.page title="Staffs">
+<.page title={gettext("Staffs")}>
<:actions>
- <.ensure_permissions user={@current_user} permissions={%{"staffs" => ["roles_edit"]}}>
- <.link patch={~p"/dashboard/staffs/roles"}>
- <.button>
- <.icon name="hero-shield-exclamation" class="w-5 h-5" />
-
-
-
-
<.table_search
id="staffs-table-name-search"
params={@params}
@@ -16,6 +8,20 @@
path={~p"/dashboard/staffs"}
placeholder={gettext("Search for users")}
/>
+
+ <.ensure_permissions user={@current_user} permissions={%{"staffs" => ["edit"]}}>
+ <.link patch={~p"/dashboard/staffs/new"}>
+ <.button>New Staff
+
+
+
+ <.ensure_permissions user={@current_user} permissions={%{"staffs" => ["roles_edit"]}}>
+ <.link patch={~p"/dashboard/staffs/roles"}>
+ <.button>
+ <.icon name="hero-shield-exclamation" class="w-5 h-5" />
+
+
+
@@ -61,45 +67,48 @@
<.modal
- :if={@live_action in [:roles]}
+ :if={@live_action in [:new, :edit]}
id="staff-modal"
show
on_cancel={JS.patch(~p"/dashboard/staffs")}
>
<.live_component
- module={SafiraWeb.Backoffice.StaffLive.RoleLive.Index}
- id="list-roles"
- title="Roles"
+ module={SafiraWeb.Backoffice.StaffLive.FormComponent}
+ id={@user.id || :new}
+ title="Edit Staff"
action={@live_action}
patch={~p"/dashboard/staffs"}
+ current_user={@current_user}
+ user={@user}
+ roles={@roles}
/>
<.modal
- :if={@live_action in [:roles_new, :roles_edit]}
+ :if={@live_action in [:roles]}
id="staff-modal"
show
on_cancel={JS.patch(~p"/dashboard/staffs")}
>
<.live_component
- module={SafiraWeb.Backoffice.StaffLive.RoleLive.FormComponent}
- id={@params["id"] || "new-role"}
- title="New Role"
+ module={SafiraWeb.Backoffice.StaffLive.RoleLive.Index}
+ id="list-roles"
+ title="Roles"
action={@live_action}
patch={~p"/dashboard/staffs"}
/>
<.modal
- :if={@live_action in [:edit]}
+ :if={@live_action in [:roles_new, :roles_edit]}
id="staff-modal"
show
on_cancel={JS.patch(~p"/dashboard/staffs")}
>
<.live_component
- module={SafiraWeb.Backoffice.StaffLive.FormComponent}
- id={@params["id"]}
- title="Edit Staff"
+ module={SafiraWeb.Backoffice.StaffLive.RoleLive.FormComponent}
+ id={@params["id"] || "new-role"}
+ title="New Role"
action={@live_action}
patch={~p"/dashboard/staffs"}
/>
diff --git a/lib/safira_web/router.ex b/lib/safira_web/router.ex
index 1ef0b54ca..f15f7bf54 100644
--- a/lib/safira_web/router.ex
+++ b/lib/safira_web/router.ex
@@ -194,6 +194,8 @@ defmodule SafiraWeb.Router do
scope "/staffs", StaffLive do
live "/", Index, :index
+ live "/new", Index, :new
+
live "/:id/edit", Index, :edit
scope "/roles" do
diff --git a/priv/repo/seeds/accounts.exs b/priv/repo/seeds/accounts.exs
index 5b0e85170..4b8da4aee 100644
--- a/priv/repo/seeds/accounts.exs
+++ b/priv/repo/seeds/accounts.exs
@@ -75,17 +75,16 @@ defmodule Safira.Repo.Seeds.Accounts do
handle = name |> String.downcase() |> String.replace(~r/\s/, "_")
user = %{
- name: name,
- handle: handle,
- email: email,
- password: "password1234"
+ "name" => name,
+ "handle" => handle,
+ "email" => email,
+ "password" => "password1234",
+ "staff" => %{
+ "role_id" => Enum.at(roles, 0).id
+ }
}
- case Accounts.register_staff_user(user) do
- {:ok, changeset} ->
- user = Repo.update!(Accounts.User.confirm_changeset(changeset))
- Accounts.create_staff(%{user_id: user.id, role_id: Enum.random(roles).id})
- {:error, changeset} ->
+ with {:error, changeset} <- Accounts.register_staff_user(user) do
Mix.shell().error(Kernel.inspect(changeset.errors))
end
end
diff --git a/test/safira/accounts_test.exs b/test/safira/accounts_test.exs
index a55f76d17..0d8aefe1c 100644
--- a/test/safira/accounts_test.exs
+++ b/test/safira/accounts_test.exs
@@ -53,8 +53,9 @@ defmodule Safira.AccountsTest do
{:error, _, changeset, _} = Accounts.register_attendee_user(%{})
assert %{
- password: ["can't be blank"],
- email: ["can't be blank"]
+ email: ["can't be blank"],
+ handle: ["can't be blank"],
+ name: ["can't be blank"]
} = errors_on(changeset)
end
@@ -105,17 +106,18 @@ defmodule Safira.AccountsTest do
describe "register_staff_user/1" do
test "requires email and password to be set" do
- {:error, changeset} = Accounts.register_staff_user(%{})
+ {:error, _, changeset, _} = Accounts.register_staff_user(%{})
assert %{
- password: ["can't be blank"],
+ handle: ["can't be blank"],
+ name: ["can't be blank"],
email: ["can't be blank"]
} = errors_on(changeset)
end
test "validates email and password when given" do
- {:error, changeset} =
- Accounts.register_staff_user(%{email: "not valid", password: "not valid"})
+ {:error, _, changeset, _} =
+ Accounts.register_staff_user(%{"email" => "not valid", "password" => "not valid"})
assert %{
email: ["must have the @ sign and no spaces"],
@@ -125,26 +127,12 @@ defmodule Safira.AccountsTest do
test "validates maximum values for email and password for security" do
too_long = String.duplicate("db", 100)
- {:error, changeset} = Accounts.register_staff_user(%{email: too_long, password: too_long})
- assert "should be at most 160 character(s)" in errors_on(changeset).email
- assert "should be at most 72 character(s)" in errors_on(changeset).password
- end
- test "validates email uniqueness" do
- %{email: email} = user_fixture()
- {:error, _changeset} = Accounts.register_staff_user(%{email: email})
-
- # Now try with the upper cased email too, to check that email case is ignored.
- {:error, _changeset} = Accounts.register_staff_user(%{email: String.upcase(email)})
- end
+ {:error, _, changeset, _} =
+ Accounts.register_staff_user(%{"email" => too_long, "password" => too_long})
- test "registers users with a hashed password" do
- email = unique_user_email()
- {:ok, user} = Accounts.register_staff_user(valid_user_attributes(email: email))
- assert user.email == email
- assert is_binary(user.hashed_password)
- assert is_nil(user.confirmed_at)
- assert is_nil(user.password)
+ assert "should be at most 160 character(s)" in errors_on(changeset).email
+ assert "should be at most 72 character(s)" in errors_on(changeset).password
end
end