spazio-solazzo/lib/spazio_solazzo/accounts/user.ex
Víctor Martínez 2cbce8ec39
refactor: make phone number optional (#9)
This pull request makes phone numbers optional for both user registrations and bookings, and updates validation, database schema, and UI to reflect this change. The main focus is to ensure that users are no longer required to provide a phone number, and that the application gracefully handles cases where a phone number is absent.

**Database & Resource Model Updates**

* Made the `phone_number` attribute in the `users` table and the `customer_phone` attribute in the `bookings` table nullable, including migration and resource snapshot updates. [[1]](diffhunk://#diff-baa6aed3674c4d6cbbebeafb076662df02dc4c25231dbd9dc9c8f0534ed1a1bfR1-R29) [[2]](diffhunk://#diff-a401f66b2ae5bfb798eb1bc2221bfeeac943e258950c90d59570b0bae05d3664R1-R244) [[3]](diffhunk://#diff-0c1180d6f6abc19b5987c8703bdee9ef67905535202f950e8327c32bd5b89d8aR1-R82)
* Updated Ash resource definitions in `user.ex` and `booking.ex` to allow `phone_number` and `customer_phone` to be `nil`. [[1]](diffhunk://#diff-9194b9d80dce091f6dcb56f784217272ae160e35454c4b4ccc8850ad5ee06e38L152-R152) [[2]](diffhunk://#diff-4b1ddd6d86899f2144c69d142883b8719c755e32c03dbda5da2188208a5ad503L55-R55) [[3]](diffhunk://#diff-4b1ddd6d86899f2144c69d142883b8719c755e32c03dbda5da2188208a5ad503L170-R170)

**Validation & Parsing Logic**

* Renamed and refactored user registration field validation to `ParseRegistrationFields`, allowing phone numbers to be omitted and trimming input values. Empty phone numbers are now treated as absent rather than as errors. [[1]](diffhunk://#diff-8ffdd76e260e3cda6f0816c8e585ae76b993a90d2519c38185a5fe22b4b49e47L1-R1) [[2]](diffhunk://#diff-8ffdd76e260e3cda6f0816c8e585ae76b993a90d2519c38185a5fe22b4b49e47R14-R60)
* Updated the authentication callback logic to trim input values and omit the phone number parameter if it is blank.

**User Interface Improvements**

* Updated registration and booking forms to indicate that phone numbers are optional, removed the required attribute, and improved placeholder text. [[1]](diffhunk://#diff-f356eb84970d8c9ee6ff1992c297b0cae07bade37ff967c1e6e0de6f8b67081cL101-R115) [[2]](diffhunk://#diff-43c0e1f7a869ee5c43a911bc10dc80cbb265a8672340ef0fa7c1d3009c047f02L92-R92)
* Updated email templates and confirmation screens to display "N/A" or "-" when phone numbers are missing. [[1]](diffhunk://#diff-48468ef2d1bb2c33b5ffb40457b77532815c7faf1830932661f665bff58b2177R6-R11) [[2]](diffhunk://#diff-3f33187b4021450b481ce53fe13166addea582c627f2cfbc99c75c7ce5c34857L10-R10) [[3]](diffhunk://#diff-43c0e1f7a869ee5c43a911bc10dc80cbb265a8672340ef0fa7c1d3009c047f02L92-R92)

**Profile Management**

* Improved profile update flow to ensure the form reflects the latest user data after saving changes.
* Made the "Full Name" field explicitly required in the profile form UI.
2026-01-15 15:57:54 +01:00

160 lines
3.8 KiB
Elixir

defmodule SpazioSolazzo.Accounts.User do
@moduledoc """
Represents a user in the system with magic link authentication.
"""
use Ash.Resource,
otp_app: :spazio_solazzo,
domain: SpazioSolazzo.Accounts,
data_layer: AshPostgres.DataLayer,
authorizers: [Ash.Policy.Authorizer],
extensions: [AshAuthentication]
authentication do
add_ons do
log_out_everywhere do
apply_on_password_change? true
end
end
tokens do
enabled? true
token_resource SpazioSolazzo.Accounts.Token
signing_secret SpazioSolazzo.Secrets
store_all_tokens? true
require_token_presence_for_authentication? true
end
strategies do
magic_link do
identity_field :email
registration_enabled? true
require_interaction? true
sender SpazioSolazzo.Accounts.User.Senders.SendMagicLinkEmail
end
remember_me :remember_me
end
end
postgres do
table "users"
repo SpazioSolazzo.Repo
end
actions do
defaults [:read]
read :get_by_email do
description "Looks up a user by their email"
argument :email, :ci_string, allow_nil?: false
get? true
filter expr(email == ^arg(:email))
end
create :sign_in_with_magic_link do
description "Sign in or register a user with magic link."
argument :token, :string do
description "The token from the magic link that was sent to the user"
allow_nil? false
end
argument :remember_me, :boolean do
description "Whether to generate a remember me token"
allow_nil? true
end
argument :name, :string do
description "User's full name (required for new users)"
allow_nil? true
end
argument :phone_number, :string do
description "User's phone number (required for new users)"
allow_nil? true
end
upsert? true
upsert_identity :unique_email
upsert_fields [:email, :name, :phone_number]
# Uses the information from the token to create or sign in the user
change AshAuthentication.Strategy.MagicLink.SignInChange
# Conditionally validate name and phone_number for new users
change SpazioSolazzo.Accounts.User.Changes.ParseRegistrationFields
change {AshAuthentication.Strategy.RememberMe.MaybeGenerateTokenChange,
strategy_name: :remember_me}
metadata :token, :string do
allow_nil? false
end
end
action :request_magic_link do
argument :email, :ci_string, allow_nil?: false
run AshAuthentication.Strategy.MagicLink.Request
end
update :update_profile do
description "Update user profile (name and phone number)"
accept [:name, :phone_number]
require_atomic? false
end
destroy :terminate_account do
description "Delete user account with optional booking data removal"
require_atomic? false
argument :delete_history, :boolean do
description "Whether to permanently delete all booking history"
default false
end
change SpazioSolazzo.Accounts.User.Changes.HandleBookingsOnAccountDeletion
end
end
policies do
bypass AshAuthentication.Checks.AshAuthenticationInteraction do
authorize_if always()
end
policy action_type(:read) do
authorize_if always()
end
policy action_type(:update) do
authorize_if expr(id == ^actor(:id))
end
policy action_type(:destroy) do
authorize_if expr(id == ^actor(:id))
end
end
attributes do
uuid_primary_key :id
attribute :email, :ci_string do
allow_nil? false
public? true
end
attribute :name, :string do
allow_nil? false
public? true
end
attribute :phone_number, :string do
allow_nil? true
public? true
end
end
identities do
identity :unique_email, [:email]
end
end