diff --git a/.drone.yml b/.drone.yml
index 0e1bc67..8c7f325 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -166,7 +166,7 @@ environment:
steps:
- name: renovate
- image: renovate/renovate:42.71
+ image: renovate/renovate:42.44
environment:
RENOVATE_CONFIG_FILE: "renovate_backend_config.js"
RENOVATE_TOKEN:
diff --git a/lib/membership/member.ex b/lib/membership/member.ex
index 1d6d96e..3a0fa5b 100644
--- a/lib/membership/member.ex
+++ b/lib/membership/member.ex
@@ -39,7 +39,6 @@ defmodule Mv.Membership.Member do
require Ash.Query
import Ash.Expr
- require Logger
# Module constants
@member_search_limit 10
@@ -74,9 +73,6 @@ defmodule Mv.Membership.Member do
create :create_member do
primary? true
-
- # Note: Custom validation function cannot be done atomically (queries DB for required custom fields)
- # In Ash 3.0, require_atomic? is not available for create actions, but the validation will still work
# Custom field values can be created along with member
argument :custom_field_values, {:array, :map}
# Allow user to be passed as argument for relationship management
@@ -421,32 +417,6 @@ defmodule Mv.Membership.Member do
{:error, field: :email, message: "is not a valid email"}
end
end
-
- # Validate required custom fields
- validate fn changeset, _ ->
- provided_values = provided_custom_field_values(changeset)
-
- case Mv.Membership.list_required_custom_fields() do
- {:ok, required_custom_fields} ->
- missing_fields = missing_required_fields(required_custom_fields, provided_values)
-
- if Enum.empty?(missing_fields) do
- :ok
- else
- build_custom_field_validation_error(missing_fields)
- end
-
- {:error, error} ->
- Logger.error(
- "Failed to load custom fields for validation: #{inspect(error)}. Required field validation cannot be performed."
- )
-
- {:error,
- field: :custom_field_values,
- message:
- "Unable to validate required custom fields. Please try again or contact support."}
- end
- end
end
attributes do
@@ -1162,127 +1132,4 @@ defmodule Mv.Membership.Member do
query
end
end
-
- # Extracts provided custom field values from changeset
- # Handles both create (from argument) and update (from existing data) scenarios
- defp provided_custom_field_values(changeset) do
- custom_field_values_arg = Ash.Changeset.get_argument(changeset, :custom_field_values)
-
- if is_nil(custom_field_values_arg) do
- extract_existing_values(changeset.data)
- else
- extract_argument_values(custom_field_values_arg)
- end
- end
-
- # Extracts custom field values from existing member data (update scenario)
- defp extract_existing_values(member_data) do
- case Ash.load(member_data, :custom_field_values) do
- {:ok, %{custom_field_values: existing_values}} ->
- Enum.reduce(existing_values, %{}, &extract_value_from_cfv/2)
-
- _ ->
- %{}
- end
- end
-
- # Extracts value from a CustomFieldValue struct
- defp extract_value_from_cfv(cfv, acc) do
- value = extract_union_value(cfv.value)
- Map.put(acc, cfv.custom_field_id, value)
- end
-
- # Extracts value from union type (map or direct value)
- defp extract_union_value(value) when is_map(value), do: Map.get(value, :value)
- defp extract_union_value(value), do: value
-
- # Extracts custom field values from provided argument (create/update scenario)
- defp extract_argument_values(custom_field_values_arg) do
- Enum.reduce(custom_field_values_arg, %{}, &extract_value_from_arg/2)
- end
-
- # Extracts value from argument map
- defp extract_value_from_arg(cfv, acc) do
- custom_field_id = Map.get(cfv, "custom_field_id")
- value_map = Map.get(cfv, "value", %{})
- actual_value = extract_value_from_map(value_map)
- Map.put(acc, custom_field_id, actual_value)
- end
-
- # Extracts value from map, supporting both "value" and "_union_value" keys
- # Also handles Ash.Union structs (which have atom keys :value and :type)
- # Uses cond instead of || to preserve false values
- defp extract_value_from_map(value_map) do
- cond do
- # Handle Ash.Union struct - check if it's a struct with __struct__ == Ash.Union
- match?({:ok, Ash.Union}, Map.fetch(value_map, :__struct__)) ->
- Map.get(value_map, :value)
-
- # Handle map with string keys
- Map.has_key?(value_map, "value") ->
- Map.get(value_map, "value")
-
- Map.has_key?(value_map, "_union_value") ->
- Map.get(value_map, "_union_value")
-
- # Handle map with atom keys
- Map.has_key?(value_map, :value) ->
- Map.get(value_map, :value)
-
- true ->
- nil
- end
- end
-
- # Finds which required custom fields are missing from provided values
- defp missing_required_fields(required_custom_fields, provided_values) do
- Enum.filter(required_custom_fields, fn cf ->
- value = Map.get(provided_values, cf.id)
- not value_present?(value, cf.value_type)
- end)
- end
-
- # Builds validation error message for missing required custom fields
- defp build_custom_field_validation_error(missing_fields) do
- # Sort missing fields alphabetically for consistent error messages
- sorted_missing_fields = Enum.sort_by(missing_fields, & &1.name)
- missing_names = Enum.map_join(sorted_missing_fields, ", ", & &1.name)
-
- {:error,
- field: :custom_field_values,
- message:
- Gettext.dgettext(MvWeb.Gettext, "default", "Required custom fields missing: %{fields}",
- fields: missing_names
- )}
- end
-
- # Helper function to check if a value is present for a given custom field type
- # Boolean: false is valid, only nil is invalid
- # String: nil or empty strings are invalid
- # Integer: nil or empty strings are invalid, 0 is valid
- # Date: nil or empty strings are invalid
- # Email: nil or empty strings are invalid
- defp value_present?(nil, _type), do: false
-
- defp value_present?(value, :boolean), do: not is_nil(value)
-
- defp value_present?(value, :string), do: is_binary(value) and String.trim(value) != ""
-
- defp value_present?(value, :integer) when is_integer(value), do: true
-
- defp value_present?(value, :integer) when is_binary(value), do: String.trim(value) != ""
-
- defp value_present?(_value, :integer), do: false
-
- defp value_present?(value, :date) when is_struct(value, Date), do: true
-
- defp value_present?(value, :date) when is_binary(value), do: String.trim(value) != ""
-
- defp value_present?(_value, :date), do: false
-
- defp value_present?(value, :email) when is_binary(value), do: String.trim(value) != ""
-
- defp value_present?(_value, :email), do: false
-
- defp value_present?(_value, _type), do: false
end
diff --git a/lib/membership/membership.ex b/lib/membership/membership.ex
index 4917c7c..f5a708b 100644
--- a/lib/membership/membership.ex
+++ b/lib/membership/membership.ex
@@ -21,9 +21,6 @@ defmodule Mv.Membership do
use Ash.Domain,
extensions: [AshAdmin.Domain, AshPhoenix]
- require Ash.Query
- import Ash.Expr
-
admin do
show? true
end
@@ -128,29 +125,6 @@ defmodule Mv.Membership do
|> Ash.update(domain: __MODULE__)
end
- @doc """
- Lists only required custom fields.
-
- This is an optimized version that filters at the database level instead of
- loading all custom fields and filtering in memory.
-
- ## Returns
-
- - `{:ok, required_custom_fields}` - List of required custom fields
- - `{:error, error}` - Error reading custom fields
-
- ## Examples
-
- iex> {:ok, required_fields} = Mv.Membership.list_required_custom_fields()
- iex> Enum.all?(required_fields, & &1.required)
- true
- """
- def list_required_custom_fields do
- Mv.Membership.CustomField
- |> Ash.Query.filter(expr(required == true))
- |> Ash.read(domain: __MODULE__)
- end
-
@doc """
Updates the member field visibility configuration.
diff --git a/lib/mv_web/components/core_components.ex b/lib/mv_web/components/core_components.ex
index ccec5a5..a1020ef 100644
--- a/lib/mv_web/components/core_components.ex
+++ b/lib/mv_web/components/core_components.ex
@@ -333,8 +333,7 @@ defmodule MvWeb.CoreComponents do
attr :error_class, :string, default: nil, doc: "the input error class to use over defaults"
attr :rest, :global,
- include:
- ~w(accept autocomplete aria-required capture cols disabled form list max maxlength min minlength
+ include: ~w(accept autocomplete capture cols disabled form list max maxlength min minlength
multiple pattern placeholder readonly required rows size step)
def input(%{field: %Phoenix.HTML.FormField{} = field} = assigns) do
@@ -354,24 +353,6 @@ defmodule MvWeb.CoreComponents do
Phoenix.HTML.Form.normalize_value("checkbox", assigns[:value])
end)
- # For checkboxes, we don't use HTML required attribute (means "must be checked")
- # Instead, we use aria-required for screen readers (WCAG 2.1, Success Criterion 3.3.2)
- # Extract required from rest and remove it, but keep aria-required if provided
- rest = assigns.rest || %{}
- is_required = Map.get(rest, :required, false)
- aria_required = Map.get(rest, :aria_required, if(is_required, do: "true", else: nil))
-
- # Remove required from rest (we don't want HTML required on checkbox)
- rest_without_required = Map.delete(rest, :required)
- # Ensure aria-required is set if field is required
- rest_final =
- if aria_required,
- do: Map.put(rest_without_required, :aria_required, aria_required),
- else: rest_without_required
-
- assigns = assign(assigns, :rest, rest_final)
- assigns = assign(assigns, :is_required, is_required)
-
~H"""