defmodule Mv.Membership do @moduledoc """ Ash Domain for membership management. ## Resources - `Member` - Club members with personal information and custom field values - `CustomFieldValue` - Dynamic custom field values attached to members - `CustomField` - Schema definitions for custom fields - `Setting` - Global application settings (singleton) ## Public API The domain exposes these main actions: - Member CRUD: `create_member/1`, `list_members/0`, `update_member/2`, `destroy_member/1` - Custom field value management: `create_custom_field_value/1`, `list_custom_field_values/0`, etc. - Custom field management: `create_custom_field/1`, `list_custom_fields/0`, etc. - Settings management: `get_settings/0`, `update_settings/2` ## Admin Interface The domain is configured with AshAdmin for management UI. """ use Ash.Domain, extensions: [AshAdmin.Domain, AshPhoenix] admin do show? true end resources do resource Mv.Membership.Member do define :create_member, action: :create_member define :list_members, action: :read define :update_member, action: :update_member define :destroy_member, action: :destroy end resource Mv.Membership.CustomFieldValue do define :create_custom_field_value, action: :create define :list_custom_field_values, action: :read define :update_custom_field_value, action: :update define :destroy_custom_field_value, action: :destroy end resource Mv.Membership.CustomField do define :create_custom_field, action: :create define :list_custom_fields, action: :read define :update_custom_field, action: :update define :destroy_custom_field, action: :destroy_with_values define :prepare_custom_field_deletion, action: :prepare_deletion, args: [:id] end resource Mv.Membership.Setting do # Note: create action exists but is not exposed via code interface # It's only used internally as fallback in get_settings/0 # Settings should be created via seed script define :update_settings, action: :update define :update_member_field_visibility, action: :update_member_field_visibility end end # Singleton pattern: Get the single settings record @doc """ Gets the global settings. Settings should normally be created via the seed script (`priv/repo/seeds.exs`). If no settings exist, this function will create them as a fallback using the `ASSOCIATION_NAME` environment variable or "Club Name" as default. ## Returns - `{:ok, settings}` - The settings record - `{:ok, nil}` - No settings exist (should not happen if seeds were run) - `{:error, error}` - Error reading settings ## Examples iex> {:ok, settings} = Mv.Membership.get_settings() iex> settings.club_name "My Club" """ def get_settings do # Try to get the first (and only) settings record case Ash.read_one(Mv.Membership.Setting, domain: __MODULE__) do {:ok, nil} -> # No settings exist - create as fallback (should normally be created via seed script) default_club_name = System.get_env("ASSOCIATION_NAME") || "Club Name" Mv.Membership.Setting |> Ash.Changeset.for_create(:create, %{club_name: default_club_name}) |> Ash.create!(domain: __MODULE__) |> then(fn settings -> {:ok, settings} end) {:ok, settings} -> {:ok, settings} {:error, error} -> {:error, error} end end @doc """ Updates the global settings. ## Parameters - `settings` - The settings record to update - `attrs` - A map of attributes to update (e.g., `%{club_name: "New Name"}`) ## Returns - `{:ok, updated_settings}` - Successfully updated settings - `{:error, error}` - Validation or update error ## Examples iex> {:ok, settings} = Mv.Membership.get_settings() iex> {:ok, updated} = Mv.Membership.update_settings(settings, %{club_name: "New Club"}) iex> updated.club_name "New Club" """ def update_settings(settings, attrs) do settings |> Ash.Changeset.for_update(:update, attrs) |> Ash.update(domain: __MODULE__) end @doc """ Updates the member field visibility configuration. This is a specialized action for updating only the member field visibility settings. It validates that all keys are valid member fields and all values are booleans. ## Parameters - `settings` - The settings record to update - `visibility_config` - A map of member field names (strings) to boolean visibility values (e.g., `%{"street" => false, "house_number" => false}`) ## Returns - `{:ok, updated_settings}` - Successfully updated settings - `{:error, error}` - Validation or update error ## Examples iex> {:ok, settings} = Mv.Membership.get_settings() iex> {:ok, updated} = Mv.Membership.update_member_field_visibility(settings, %{"street" => false, "house_number" => false}) iex> updated.member_field_visibility %{"street" => false, "house_number" => false} """ def update_member_field_visibility(settings, visibility_config) do settings |> Ash.Changeset.for_update(:update_member_field_visibility, %{ member_field_visibility: visibility_config }) |> Ash.update(domain: __MODULE__) end end