feat: adds member visibility settings
This commit is contained in:
parent
831149f463
commit
397cbde9d6
3 changed files with 178 additions and 2 deletions
|
|
@ -434,6 +434,70 @@ defmodule Mv.Membership.Member do
|
||||||
identity :unique_email, [:email]
|
identity :unique_email, [:email]
|
||||||
end
|
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 """
|
@doc """
|
||||||
Performs fuzzy search on members using PostgreSQL trigram similarity.
|
Performs fuzzy search on members using PostgreSQL trigram similarity.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ defmodule Mv.Membership do
|
||||||
# It's only used internally as fallback in get_settings/0
|
# It's only used internally as fallback in get_settings/0
|
||||||
# Settings should be created via seed script
|
# Settings should be created via seed script
|
||||||
define :update_settings, action: :update
|
define :update_settings, action: :update
|
||||||
|
define :update_member_field_visibility, action: :update_member_field_visibility
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -123,4 +124,37 @@ defmodule Mv.Membership do
|
||||||
|> Ash.Changeset.for_update(:update, attrs)
|
|> Ash.Changeset.for_update(:update, attrs)
|
||||||
|> Ash.update(domain: __MODULE__)
|
|> Ash.update(domain: __MODULE__)
|
||||||
end
|
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
|
end
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ defmodule Mv.Membership.Setting do
|
||||||
|
|
||||||
## Attributes
|
## Attributes
|
||||||
- `club_name` - The name of the association/club (required, cannot be empty)
|
- `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
|
## Singleton Pattern
|
||||||
This resource uses a singleton pattern - there should only be one settings record.
|
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
|
# Update club name
|
||||||
{:ok, updated} = Mv.Membership.update_settings(settings, %{club_name: "New 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,
|
use Ash.Resource,
|
||||||
domain: Mv.Membership,
|
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
|
# Used only as fallback in get_settings/0 if settings don't exist
|
||||||
# Settings should normally be created via seed script
|
# Settings should normally be created via seed script
|
||||||
create :create do
|
create :create do
|
||||||
accept [:club_name]
|
accept [:club_name, :member_field_visibility]
|
||||||
end
|
end
|
||||||
|
|
||||||
update :update do
|
update :update do
|
||||||
primary? true
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
validations do
|
validations do
|
||||||
validate present(:club_name), on: [:create, :update]
|
validate present(:club_name), on: [:create, :update]
|
||||||
validate string_length(:club_name, min: 1), 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
|
end
|
||||||
|
|
||||||
attributes do
|
attributes do
|
||||||
|
|
@ -75,6 +148,11 @@ defmodule Mv.Membership.Setting do
|
||||||
min_length: 1
|
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()
|
timestamps()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue