diff --git a/lib/aprsme/accounts.ex b/lib/aprsme/accounts.ex index dd34fe6..6868627 100644 --- a/lib/aprsme/accounts.ex +++ b/lib/aprsme/accounts.ex @@ -181,6 +181,46 @@ defmodule Aprsme.Accounts do UserNotifier.deliver_update_email_instructions(user, update_email_url_fun.(encoded_token)) end + @doc """ + Returns an `%Ecto.Changeset{}` for changing the user callsign. + + ## Examples + + iex> change_user_callsign(user) + %Ecto.Changeset{data: %User{}} + + """ + def change_user_callsign(user, attrs \\ %{}) do + User.callsign_changeset(user, attrs, validate_callsign: false) + end + + @doc """ + Updates the user callsign with password validation. + + ## Examples + + iex> update_user_callsign(user, "valid password", %{callsign: "K1ABC"}) + {:ok, %User{}} + + iex> update_user_callsign(user, "invalid password", %{callsign: "K1ABC"}) + {:error, %Ecto.Changeset{}} + + """ + def update_user_callsign(user, password, attrs) do + changeset = + user + |> User.callsign_changeset(attrs) + |> User.validate_current_password(password) + + Ecto.Multi.new() + |> Ecto.Multi.update(:user, changeset) + |> Repo.transaction() + |> case do + {:ok, %{user: user}} -> {:ok, user} + {:error, :user, changeset, _} -> {:error, changeset} + end + end + @doc """ Returns an `%Ecto.Changeset{}` for changing the user password. diff --git a/lib/aprsme/accounts/user.ex b/lib/aprsme/accounts/user.ex index a311716..b6e16fe 100644 --- a/lib/aprsme/accounts/user.ex +++ b/lib/aprsme/accounts/user.ex @@ -6,6 +6,7 @@ defmodule Aprsme.Accounts.User do schema "users" do field(:email, :string) + field(:callsign, :string) field(:password, :string, virtual: true, redact: true) field(:hashed_password, :string, redact: true) field(:confirmed_at, :naive_datetime) @@ -38,8 +39,9 @@ defmodule Aprsme.Accounts.User do """ def registration_changeset(user, attrs, opts \\ []) do user - |> cast(attrs, [:email, :password]) + |> cast(attrs, [:email, :password, :callsign]) |> validate_email(opts) + |> validate_callsign(opts) |> validate_password(opts) end @@ -51,6 +53,17 @@ defmodule Aprsme.Accounts.User do |> maybe_validate_unique_email(opts) end + defp validate_callsign(changeset, opts) do + changeset + |> validate_required([:callsign]) + |> validate_format(:callsign, ~r/^[A-Z]{1,2}[0-9]{1,2}[A-Z]{1,3}$/i, + message: "must be a valid amateur radio callsign" + ) + |> update_change(:callsign, &String.upcase/1) + |> validate_length(:callsign, min: 3, max: 10) + |> maybe_validate_unique_callsign(opts) + end + defp validate_password(changeset, opts) do changeset |> validate_required([:password]) @@ -95,6 +108,19 @@ defmodule Aprsme.Accounts.User do defp do_validate_unique_email(changeset, false), do: changeset + defp maybe_validate_unique_callsign(changeset, opts) do + validate_callsign? = Keyword.get(opts, :validate_callsign, true) + do_validate_unique_callsign(changeset, validate_callsign?) + end + + defp do_validate_unique_callsign(changeset, true) do + changeset + |> unsafe_validate_unique(:callsign, Aprsme.Repo) + |> unique_constraint(:callsign) + end + + defp do_validate_unique_callsign(changeset, false), do: changeset + @doc """ A user changeset for changing the email. @@ -110,6 +136,21 @@ defmodule Aprsme.Accounts.User do end end + @doc """ + A user changeset for changing the callsign. + + It requires the callsign to change otherwise an error is added. + """ + def callsign_changeset(user, attrs, opts \\ []) do + user + |> cast(attrs, [:callsign]) + |> validate_callsign(opts) + |> case do + %{changes: %{callsign: _}} = changeset -> changeset + %{} = changeset -> add_error(changeset, :callsign, "did not change") + end + end + @doc """ A user changeset for changing the password. diff --git a/lib/aprsme_web/live/user_registration_live.ex b/lib/aprsme_web/live/user_registration_live.ex index a1accf8..5c618a2 100644 --- a/lib/aprsme_web/live/user_registration_live.ex +++ b/lib/aprsme_web/live/user_registration_live.ex @@ -60,6 +60,28 @@ defmodule AprsmeWeb.UserRegistrationLive do +
+ + + +
+