WIP feat: member user relation

This commit is contained in:
Moritz 2025-07-24 20:15:01 +02:00
parent ba79261d1d
commit 0dddeeb7a6
Signed by: moritz
GPG key ID: 1020A035E5DD0824
35 changed files with 1208 additions and 192 deletions

View file

@ -21,4 +21,15 @@ defmodule Mv.Accounts do
resource Mv.Accounts.Token
end
@doc """
Register a new user with password using AshAuthentication's standard action.
This creates a user and the notifier will automatically create a member.
"""
def register_with_password(params) do
# Use AshAuthentication's standard register_with_password action
Mv.Accounts.User
|> Ash.Changeset.for_create(:register_with_password, params)
|> Ash.create(domain: __MODULE__)
end
end

View file

@ -5,7 +5,8 @@ defmodule Mv.Accounts.User do
use Ash.Resource,
domain: Mv.Accounts,
data_layer: AshPostgres.DataLayer,
extensions: [AshAuthentication]
extensions: [AshAuthentication],
notifiers: [Mv.Accounts.User.MemberCreationNotifier]
# authorizers: [Ash.Policy.Authorizer]
@ -64,11 +65,11 @@ defmodule Mv.Accounts.User do
defaults [:read, :create, :destroy, :update]
create :create_user do
accept [:email]
accept [:email, :member_id]
end
update :update_user do
accept [:email]
accept [:email, :member_id]
end
# Admin action for direct password changes in admin panel
@ -121,9 +122,16 @@ defmodule Mv.Accounts.User do
# Global validations - applied to all relevant actions
validations do
# Password strength policy: minimum 8 characters for all password-related actions
# Password strength policy: minimum 8 characters
# Note: register_with_password has built-in AshAuthentication validation, but admin_set_password doesn't
validate string_length(:password, min: 8) do
where action_is([:register_with_password, :admin_set_password])
# Only needed for admin actions, AshAuthentication handles register_with_password
where action_is([:admin_set_password])
end
# Email uniqueness for registration actions
validate attribute_does_not_equal(:email, nil) do
where action_is([:register_with_password, :register_with_rauthy])
end
end
@ -143,6 +151,7 @@ defmodule Mv.Accounts.User do
attribute :email, :ci_string, allow_nil?: false, public?: true
attribute :hashed_password, :string, sensitive?: true, allow_nil?: true
attribute :oidc_id, :string, allow_nil?: true
attribute :admin?, :boolean, allow_nil?: false, default: false, public?: true
end
relationships do
@ -152,6 +161,7 @@ defmodule Mv.Accounts.User do
identities do
identity :unique_email, [:email]
identity :unique_oidc_id, [:oidc_id]
identity :unique_member_id, [:member_id]
end
# You can customize this if you wish, but this is a safe default that

View file

@ -0,0 +1,71 @@
defmodule Mv.Accounts.User.MemberCreationNotifier do
@moduledoc """
Notifier that automatically creates a member for newly registered users.
This runs after user creation/registration and ensures every user has an associated member.
It's designed to work with AshAuthentication without interfering with LiveView integration.
"""
use Ash.Notifier
require Logger
@impl Ash.Notifier
def notify(%Ash.Notifier.Notification{
action: %{name: action_name},
resource: Mv.Accounts.User,
data: user
})
when action_name in [:register_with_password, :register_with_rauthy, :create_user] do
# Only create member if user doesn't already have one
if should_create_member?(user) do
create_member_for_user(user)
end
:ok
end
@impl Ash.Notifier
def notify(_), do: :ok
defp should_create_member?(user) do
# Check if user has a member_id and if that member actually exists
case user.member_id do
nil ->
true
member_id ->
case Ash.get(Mv.Membership.Member, member_id, domain: Mv.Membership) do
{:ok, _member} -> false
{:error, _} -> true
end
end
end
defp create_member_for_user(user) do
member_params = %{
email: to_string(user.email),
first_name: "User",
last_name: "Generated"
}
case Mv.Membership.create_member(member_params) do
{:ok, member} ->
# Update user with member_id
case Ash.Changeset.for_update(user, :update_user, %{member_id: member.id})
|> Ash.update(domain: Mv.Accounts) do
{:ok, _updated_user} ->
Logger.info(
"Successfully created and assigned member #{member.id} to user #{user.id}"
)
{:error, error} ->
Logger.warning(
"Failed to assign member #{member.id} to user #{user.id}: #{inspect(error)}"
)
end
{:error, error} ->
Logger.warning("Failed to create member for user #{user.id}: #{inspect(error)}")
end
end
end