spazio-solazzo/lib/spazio_solazzo/accounts/user.ex
Víctor Martínez bbc2f08215
feat: build an admin dashboard (#11)
* refactor: update button colors

* feat: add dashboard panel for admins only & update header styles
2026-02-01 19:30:58 +01:00

181 lines
4.3 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
# Make sure it is loaded in all read actions
preparations do
prepare build(load: [:is_admin])
end
# Make sure it is loaded in all write actions
changes do
change load(:is_admin)
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
attribute :role, :atom do
allow_nil? false
public? false
constraints one_of: [:customer, :admin]
default :customer
end
end
calculations do
calculate :is_admin, :boolean, expr(role == :admin)
end
identities do
identity :unique_email, [:email]
end
end