feat: adds member visibility settings

This commit is contained in:
carla 2025-12-02 09:23:23 +01:00 committed by Rafael Epplée
parent 831149f463
commit 397cbde9d6
3 changed files with 178 additions and 2 deletions

View file

@ -434,6 +434,70 @@ defmodule Mv.Membership.Member do
identity :unique_email, [:email]
end
@doc """
Checks if a member field should be shown in the overview.
Reads the visibility configuration from Settings resource. If a field is not
configured in settings, it defaults to `true` (visible).
## Parameters
- `field` - Atom representing the member field name (e.g., `:email`, `:street`)
## Returns
- `true` if the field should be shown in overview (default)
- `false` if the field is configured as hidden in settings
## Examples
iex> Member.show_in_overview?(:email)
true
iex> Member.show_in_overview?(:street)
true # or false if configured in settings
"""
@spec show_in_overview?(atom()) :: boolean()
def show_in_overview?(field) when is_atom(field) do
case Mv.Membership.get_settings() do
{:ok, settings} ->
visibility_config = settings.member_field_visibility || %{}
# Normalize map keys to atoms (JSONB may return string keys)
normalized_config = normalize_visibility_config(visibility_config)
# Get value from normalized config, default to true
Map.get(normalized_config, field, true)
{:error, _} ->
# If settings can't be loaded, default to visible
true
end
end
def show_in_overview?(_), do: true
# Normalizes visibility config map keys from strings to atoms.
# JSONB in PostgreSQL converts atom keys to string keys when storing.
defp normalize_visibility_config(config) when is_map(config) do
Enum.reduce(config, %{}, fn
{key, value}, acc when is_atom(key) ->
Map.put(acc, key, value)
{key, value}, acc when is_binary(key) ->
try do
atom_key = String.to_existing_atom(key)
Map.put(acc, atom_key, value)
rescue
ArgumentError ->
acc
end
_, acc ->
acc
end)
end
defp normalize_visibility_config(_), do: %{}
@doc """
Performs fuzzy search on members using PostgreSQL trigram similarity.

View file

@ -53,6 +53,7 @@ defmodule Mv.Membership do
# 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
@ -123,4 +124,37 @@ defmodule Mv.Membership do
|> 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 (atoms) 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

View file

@ -9,6 +9,8 @@ defmodule Mv.Membership.Setting do
## Attributes
- `club_name` - The name of the association/club (required, cannot be empty)
- `member_field_visibility` - JSONB map storing visibility configuration for member fields
(e.g., `%{street: false, house_number: false}`). Fields not in the map default to `true`.
## Singleton Pattern
This resource uses a singleton pattern - there should only be one settings record.
@ -28,6 +30,9 @@ defmodule Mv.Membership.Setting do
# Update club name
{:ok, updated} = Mv.Membership.update_settings(settings, %{club_name: "New Name"})
# Update member field visibility
{:ok, updated} = Mv.Membership.update_member_field_visibility(settings, %{street: false, house_number: false})
"""
use Ash.Resource,
domain: Mv.Membership,
@ -49,18 +54,86 @@ defmodule Mv.Membership.Setting do
# Used only as fallback in get_settings/0 if settings don't exist
# Settings should normally be created via seed script
create :create do
accept [:club_name]
accept [:club_name, :member_field_visibility]
end
update :update do
primary? true
accept [:club_name]
require_atomic? false
accept [:club_name, :member_field_visibility]
end
update :update_member_field_visibility do
description "Updates the visibility configuration for member fields in the overview"
require_atomic? false
accept [:member_field_visibility]
change fn changeset, _context ->
visibility = Ash.Changeset.get_attribute(changeset, :member_field_visibility)
if visibility && is_map(visibility) do
valid_fields = Mv.Constants.member_fields()
# Normalize keys to atoms (JSONB may return string keys)
invalid_keys =
Enum.filter(visibility, fn {key, _value} ->
atom_key =
if is_atom(key) do
key
else
try do
String.to_existing_atom(key)
rescue
ArgumentError -> nil
end
end
atom_key && atom_key not in valid_fields
end)
|> Enum.map(fn {key, _value} -> key end)
if Enum.empty?(invalid_keys) do
changeset
else
Ash.Changeset.add_error(
changeset,
field: :member_field_visibility,
message: "Invalid member field keys: #{inspect(invalid_keys)}"
)
end
else
changeset
end
end
end
end
validations do
validate present(:club_name), on: [:create, :update]
validate string_length(:club_name, min: 1), on: [:create, :update]
# Validate that member_field_visibility map contains only boolean values
# This allows dynamic fields without hardcoding specific field names
validate fn changeset, _context ->
visibility = Ash.Changeset.get_attribute(changeset, :member_field_visibility)
if visibility && is_map(visibility) do
invalid_entries =
Enum.filter(visibility, fn {_key, value} ->
not is_boolean(value)
end)
if Enum.empty?(invalid_entries) do
:ok
else
{:error,
field: :member_field_visibility,
message: "All values in member_field_visibility must be booleans"}
end
else
:ok
end
end,
on: [:create, :update]
end
attributes do
@ -75,6 +148,11 @@ defmodule Mv.Membership.Setting do
min_length: 1
]
attribute :member_field_visibility, :map,
allow_nil?: true,
public?: true,
description: "Configuration for member field visibility in overview (JSONB map). Keys are member field names (atoms), values are booleans."
timestamps()
end
end