Code documentation and refactoring #201
28 changed files with 689 additions and 55 deletions
|
|
@ -158,11 +158,11 @@
|
|||
{Credo.Check.Warning.UnusedRegexOperation, []},
|
||||
{Credo.Check.Warning.UnusedStringOperation, []},
|
||||
{Credo.Check.Warning.UnusedTupleOperation, []},
|
||||
{Credo.Check.Warning.WrongTestFileExtension, []}
|
||||
{Credo.Check.Warning.WrongTestFileExtension, []},
|
||||
# Module documentation check (enabled after adding @moduledoc to all modules)
|
||||
{Credo.Check.Readability.ModuleDoc, []}
|
||||
],
|
||||
disabled: [
|
||||
# Checks disabled by the Mitgliederverwaltung Team
|
||||
{Credo.Check.Readability.ModuleDoc, []},
|
||||
#
|
||||
# Checks scheduled for next check update (opt-in for now)
|
||||
{Credo.Check.Refactor.UtcNowTruncate, []},
|
||||
|
|
|
|||
|
|
@ -917,14 +917,16 @@ mix credo --strict
|
|||
|
||||
- Consistency checks (spacing, line endings, parameter patterns)
|
||||
- Design checks (FIXME/TODO tags, alias usage)
|
||||
- Readability checks (max line length: 120, module/function names)
|
||||
- Readability checks (max line length: 120, module/function names, **module documentation**)
|
||||
- Refactoring opportunities (cyclomatic complexity, nesting)
|
||||
- Warnings (unused operations, unsafe operations)
|
||||
|
||||
**Disabled Checks:**
|
||||
**Documentation Enforcement:**
|
||||
|
||||
- `Credo.Check.Readability.ModuleDoc` - Disabled by team decision
|
||||
(Still encouraged to add module docs for public modules)
|
||||
- ✅ `Credo.Check.Readability.ModuleDoc` - **ENABLED** (as of November 2025)
|
||||
- All modules require `@moduledoc` documentation
|
||||
- Current coverage: 51 @moduledoc declarations across 47 modules (100% core modules)
|
||||
- CI pipeline enforces documentation standards
|
||||
|
||||
**Address Credo Issues:**
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,37 @@
|
|||
defmodule Mv.Membership.Email do
|
||||
@moduledoc """
|
||||
Custom Ash type for validated email addresses.
|
||||
|
||||
## Overview
|
||||
This type extends `:string` with email-specific validation constraints.
|
||||
It ensures that email values stored in Property resources are valid email
|
||||
addresses according to a standard regex pattern.
|
||||
|
||||
## Validation Rules
|
||||
- Minimum length: 5 characters
|
||||
- Maximum length: 254 characters (RFC 5321 maximum)
|
||||
- Pattern: Standard email format (username@domain.tld)
|
||||
- Automatic trimming of leading/trailing whitespace
|
||||
|
||||
## Usage
|
||||
This type is used in the Property union type for properties with
|
||||
`value_type: :email` in PropertyType definitions.
|
||||
|
||||
## Example
|
||||
# In a property type definition
|
||||
PropertyType.create!(%{
|
||||
name: "work_email",
|
||||
value_type: :email
|
||||
})
|
||||
|
||||
# Valid values
|
||||
"user@example.com"
|
||||
"first.last@company.co.uk"
|
||||
|
||||
# Invalid values
|
||||
"not-an-email" # Missing @ and domain
|
||||
"a@b" # Too short
|
||||
"""
|
||||
@match_pattern ~S/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/
|
||||
@match_regex Regex.compile!(@match_pattern)
|
||||
@min_length 5
|
||||
|
|
|
|||
|
|
@ -1,4 +1,36 @@
|
|||
defmodule Mv.Membership.Member do
|
||||
@moduledoc """
|
||||
Ash resource representing a club member.
|
||||
|
||||
## Overview
|
||||
Members are the core entity in the membership management system. Each member
|
||||
can have:
|
||||
- Personal information (name, email, phone, address)
|
||||
- Optional link to a User account (1:1 relationship)
|
||||
- Dynamic custom properties via PropertyType system
|
||||
- Full-text searchable profile
|
||||
|
||||
## Email Synchronization
|
||||
When a member is linked to a user account, emails are automatically synchronized
|
||||
bidirectionally. User.email is the source of truth on initial link.
|
||||
See `Mv.EmailSync` for details.
|
||||
|
||||
## Relationships
|
||||
- `has_many :properties` - Dynamic custom fields
|
||||
- `has_one :user` - Optional authentication account link
|
||||
|
||||
## Validations
|
||||
- Required: first_name, last_name, email
|
||||
- Email format validation (using EctoCommons.EmailValidator)
|
||||
- Phone number format: international format with 6-20 digits
|
||||
- Postal code format: exactly 5 digits (German format)
|
||||
- Date validations: birth_date and join_date not in future, exit_date after join_date
|
||||
- Email uniqueness: prevents conflicts with unlinked users
|
||||
|
||||
## Full-Text Search
|
||||
Members have a `search_vector` attribute (tsvector) that is automatically
|
||||
updated via database trigger. Search includes name, email, notes, and contact fields.
|
||||
"""
|
||||
use Ash.Resource,
|
||||
domain: Mv.Membership,
|
||||
data_layer: AshPostgres.DataLayer
|
||||
|
|
|
|||
|
|
@ -1,4 +1,21 @@
|
|||
defmodule Mv.Membership do
|
||||
@moduledoc """
|
||||
Ash Domain for membership management.
|
||||
|
||||
## Resources
|
||||
- `Member` - Club members with personal information and custom properties
|
||||
- `Property` - Dynamic custom field values attached to members
|
||||
- `PropertyType` - Schema definitions for custom properties
|
||||
|
||||
## Public API
|
||||
The domain exposes these main actions:
|
||||
- Member CRUD: `create_member/1`, `list_members/0`, `update_member/2`, `destroy_member/1`
|
||||
- Property management: `create_property/1`, `list_property/0`, etc.
|
||||
- PropertyType management: `create_property_type/1`, `list_property_types/0`, etc.
|
||||
|
||||
## Admin Interface
|
||||
The domain is configured with AshAdmin for management UI.
|
||||
"""
|
||||
use Ash.Domain,
|
||||
extensions: [AshAdmin.Domain, AshPhoenix]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,36 @@
|
|||
defmodule Mv.Membership.Property do
|
||||
@moduledoc """
|
||||
Ash resource representing a custom property value for a member.
|
||||
|
||||
## Overview
|
||||
Properties implement the Entity-Attribute-Value (EAV) pattern, allowing
|
||||
dynamic custom fields to be attached to members. Each property links a
|
||||
member to a property type and stores the actual value.
|
||||
|
||||
## Value Storage
|
||||
Values are stored using Ash's union type with JSONB storage format:
|
||||
```json
|
||||
{
|
||||
"type": "string",
|
||||
"value": "example"
|
||||
}
|
||||
```
|
||||
|
||||
## Supported Types
|
||||
- `:string` - Text data
|
||||
- `:integer` - Numeric data
|
||||
- `:boolean` - True/false flags
|
||||
- `:date` - Date values
|
||||
- `:email` - Validated email addresses (custom type)
|
||||
|
||||
## Relationships
|
||||
- `belongs_to :member` - The member this property belongs to (CASCADE delete)
|
||||
- `belongs_to :property_type` - The property type definition
|
||||
|
||||
## Constraints
|
||||
- Each member can have only one property per property type (unique composite index)
|
||||
- Properties are deleted when the associated member is deleted (CASCADE)
|
||||
"""
|
||||
use Ash.Resource,
|
||||
domain: Mv.Membership,
|
||||
data_layer: AshPostgres.DataLayer
|
||||
|
|
|
|||
|
|
@ -1,4 +1,48 @@
|
|||
defmodule Mv.Membership.PropertyType do
|
||||
@moduledoc """
|
||||
Ash resource defining the schema for custom member properties.
|
||||
|
||||
## Overview
|
||||
PropertyTypes define the "schema" for custom fields in the membership system.
|
||||
Each PropertyType specifies the name, data type, and behavior of a custom field
|
||||
that can be attached to members via Property resources.
|
||||
|
||||
## Attributes
|
||||
- `name` - Unique identifier for the property (e.g., "phone_mobile", "birthday")
|
||||
- `value_type` - Data type constraint (`:string`, `:integer`, `:boolean`, `:date`, `:email`)
|
||||
- `description` - Optional human-readable description
|
||||
- `immutable` - If true, property values cannot be changed after creation
|
||||
- `required` - If true, all members must have this property (future feature)
|
||||
|
||||
## Supported Value Types
|
||||
- `:string` - Text data (unlimited length)
|
||||
- `:integer` - Numeric data (64-bit integers)
|
||||
- `:boolean` - True/false flags
|
||||
- `:date` - Date values (no time component)
|
||||
- `:email` - Validated email addresses
|
||||
|
||||
## Relationships
|
||||
- `has_many :properties` - All property values of this type
|
||||
|
||||
## Constraints
|
||||
- Name must be unique across all property types
|
||||
- Cannot delete a property type that has existing property values (RESTRICT)
|
||||
|
||||
## Examples
|
||||
# Create a new property type
|
||||
PropertyType.create!(%{
|
||||
name: "phone_mobile",
|
||||
value_type: :string,
|
||||
description: "Mobile phone number"
|
||||
})
|
||||
|
||||
# Create a required property type
|
||||
PropertyType.create!(%{
|
||||
name: "emergency_contact",
|
||||
value_type: :string,
|
||||
required: true
|
||||
})
|
||||
"""
|
||||
use Ash.Resource,
|
||||
domain: Mv.Membership,
|
||||
data_layer: AshPostgres.DataLayer
|
||||
|
|
|
|||
|
|
@ -10,6 +10,20 @@ defmodule Mv.Accounts.User.Senders.SendNewUserConfirmationEmail do
|
|||
|
||||
alias Mv.Mailer
|
||||
|
||||
@doc """
|
||||
Sends a confirmation email to a new user.
|
||||
|
||||
This function is called automatically by AshAuthentication when a new
|
||||
user registers and needs to confirm their email address.
|
||||
|
||||
## Parameters
|
||||
- `user` - The user record who needs to confirm their email
|
||||
- `token` - The confirmation token to include in the email link
|
||||
- `_opts` - Additional options (unused)
|
||||
|
||||
## Returns
|
||||
The Swoosh.Email delivery result from `Mailer.deliver!/1`.
|
||||
"""
|
||||
@impl true
|
||||
def send(user, token, _) do
|
||||
new()
|
||||
|
|
|
|||
|
|
@ -10,6 +10,20 @@ defmodule Mv.Accounts.User.Senders.SendPasswordResetEmail do
|
|||
|
||||
alias Mv.Mailer
|
||||
|
||||
@doc """
|
||||
Sends a password reset email to a user.
|
||||
|
||||
This function is called automatically by AshAuthentication when a user
|
||||
requests a password reset.
|
||||
|
||||
## Parameters
|
||||
- `user` - The user record requesting the password reset
|
||||
- `token` - The password reset token to include in the email link
|
||||
- `_opts` - Additional options (unused)
|
||||
|
||||
## Returns
|
||||
The Swoosh.Email delivery result from `Mailer.deliver!/1`.
|
||||
"""
|
||||
@impl true
|
||||
def send(user, token, _) do
|
||||
new()
|
||||
|
|
|
|||
|
|
@ -9,6 +9,22 @@ defmodule Mv.Accounts.User.Validations.EmailNotUsedByOtherMember do
|
|||
"""
|
||||
use Ash.Resource.Validation
|
||||
|
||||
@doc """
|
||||
Validates email uniqueness across linked User-Member pairs.
|
||||
|
||||
This validation ensures that when a user is linked to a member, their email
|
||||
does not conflict with another member's email. It only runs when necessary
|
||||
to avoid blocking valid operations (see `@moduledoc` for trigger conditions).
|
||||
|
||||
## Parameters
|
||||
- `changeset` - The Ash changeset being validated
|
||||
- `_opts` - Options passed to the validation (unused)
|
||||
- `_context` - Ash context map (unused)
|
||||
|
||||
## Returns
|
||||
- `:ok` if validation passes or should be skipped
|
||||
- `{:error, field: :email, message: ..., value: ...}` if validation fails
|
||||
"""
|
||||
@impl true
|
||||
def validate(changeset, _opts, _context) do
|
||||
email_changing? = Ash.Changeset.changing_attribute?(changeset, :email)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,21 @@ defmodule Mv.EmailSync.Changes.SyncMemberEmailToUser do
|
|||
use Ash.Resource.Change
|
||||
alias Mv.EmailSync.{Helpers, Loader}
|
||||
|
||||
@doc """
|
||||
Implements the email synchronization from Member to User.
|
||||
|
||||
This function is called automatically by Ash when the configured trigger
|
||||
conditions are met (see `@moduledoc` for trigger details).
|
||||
|
||||
## Parameters
|
||||
- `changeset` - The Ash changeset being processed
|
||||
- `_opts` - Options passed to the change (unused)
|
||||
- `context` - Ash context map containing metadata (e.g., `:syncing_email` flag)
|
||||
|
||||
## Returns
|
||||
Modified changeset with email synchronization applied, or original changeset
|
||||
if recursion detected.
|
||||
"""
|
||||
@impl true
|
||||
def change(changeset, _opts, context) do
|
||||
# Only recursion protection needed - trigger logic is in `where` clauses
|
||||
|
|
|
|||
|
|
@ -12,6 +12,21 @@ defmodule Mv.EmailSync.Changes.SyncUserEmailToMember do
|
|||
use Ash.Resource.Change
|
||||
alias Mv.EmailSync.{Helpers, Loader}
|
||||
|
||||
@doc """
|
||||
Implements the email synchronization from User to Member.
|
||||
|
||||
This function is called automatically by Ash when the configured trigger
|
||||
conditions are met (see `@moduledoc` for trigger details).
|
||||
|
||||
## Parameters
|
||||
- `changeset` - The Ash changeset being processed
|
||||
- `_opts` - Options passed to the change (unused)
|
||||
- `context` - Ash context map containing metadata (e.g., `:syncing_email` flag)
|
||||
|
||||
## Returns
|
||||
Modified changeset with email synchronization applied, or original changeset
|
||||
if recursion detected.
|
||||
"""
|
||||
@impl true
|
||||
def change(changeset, _opts, context) do
|
||||
# Only recursion protection needed - trigger logic is in `where` clauses
|
||||
|
|
|
|||
|
|
@ -9,6 +9,22 @@ defmodule Mv.Membership.Member.Validations.EmailNotUsedByOtherUser do
|
|||
"""
|
||||
use Ash.Resource.Validation
|
||||
|
||||
@doc """
|
||||
Validates email uniqueness across linked Member-User pairs.
|
||||
|
||||
This validation ensures that when a member is linked to a user, their email
|
||||
does not conflict with another user's email. It only runs when necessary
|
||||
to avoid blocking valid operations (see `@moduledoc` for trigger conditions).
|
||||
|
||||
## Parameters
|
||||
- `changeset` - The Ash changeset being validated
|
||||
- `_opts` - Options passed to the validation (unused)
|
||||
- `_context` - Ash context map (unused)
|
||||
|
||||
## Returns
|
||||
- `:ok` if validation passes or should be skipped
|
||||
- `{:error, field: :email, message: ..., value: ...}` if validation fails
|
||||
"""
|
||||
@impl true
|
||||
def validate(changeset, _opts, _context) do
|
||||
email_changing? = Ash.Changeset.changing_attribute?(changeset, :email)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,23 @@
|
|||
defmodule Mv.Secrets do
|
||||
@moduledoc """
|
||||
Secret provider for AshAuthentication.
|
||||
|
||||
## Purpose
|
||||
Provides runtime configuration secrets for Ash Authentication strategies,
|
||||
particularly for OIDC (Rauthy) authentication.
|
||||
|
||||
## Configuration Source
|
||||
Secrets are read from the `:rauthy` key in the application configuration,
|
||||
which is typically set in `config/runtime.exs` from environment variables:
|
||||
- `OIDC_CLIENT_ID`
|
||||
- `OIDC_CLIENT_SECRET`
|
||||
- `OIDC_BASE_URL`
|
||||
- `OIDC_REDIRECT_URI`
|
||||
|
||||
## Usage
|
||||
This module is automatically called by AshAuthentication when resolving
|
||||
secrets for the User resource's OIDC strategy.
|
||||
"""
|
||||
use AshAuthentication.Secret
|
||||
|
||||
def secret_for(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,16 @@
|
|||
defmodule MvWeb.AuthOverrides do
|
||||
@moduledoc """
|
||||
UI customizations for AshAuthentication Phoenix components.
|
||||
|
||||
## Overrides
|
||||
- `SignIn` - Restricts form width to prevent full-width display
|
||||
- `Banner` - Replaces default logo with "Mitgliederverwaltung" text
|
||||
- `HorizontalRule` - Translates "or" text to German
|
||||
|
||||
## Documentation
|
||||
For complete reference on available overrides, see:
|
||||
https://hexdocs.pm/ash_authentication_phoenix/ui-overrides.html
|
||||
"""
|
||||
use AshAuthentication.Phoenix.Overrides
|
||||
use Gettext, backend: MvWeb.Gettext
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,33 @@
|
|||
defmodule MvWeb.MemberLive.Form do
|
||||
@moduledoc """
|
||||
LiveView form for creating and editing members.
|
||||
|
||||
## Features
|
||||
- Create new members with personal information
|
||||
- Edit existing member details
|
||||
- Manage custom properties (dynamic fields)
|
||||
- Real-time validation with visual feedback
|
||||
- Link/unlink user accounts
|
||||
|
||||
## Form Fields
|
||||
**Required:**
|
||||
- first_name, last_name, email
|
||||
|
||||
**Optional:**
|
||||
- birth_date, phone_number, address fields (city, street, house_number, postal_code)
|
||||
- join_date, exit_date
|
||||
- paid status
|
||||
- notes
|
||||
|
||||
## Custom Properties
|
||||
Members can have dynamic custom properties defined by PropertyTypes.
|
||||
The form dynamically renders inputs based on available PropertyTypes.
|
||||
|
||||
## Events
|
||||
- `validate` - Real-time form validation
|
||||
- `save` - Submit form (create or update member)
|
||||
- Property management events for adding/removing custom fields
|
||||
"""
|
||||
use MvWeb, :live_view
|
||||
|
||||
@impl true
|
||||
|
|
|
|||
|
|
@ -1,6 +1,37 @@
|
|||
defmodule MvWeb.MemberLive.Index do
|
||||
@moduledoc """
|
||||
LiveView for displaying and managing the member list.
|
||||
|
||||
## Features
|
||||
- Full-text search across member profiles using PostgreSQL tsvector
|
||||
- Sortable columns (name, email, address fields)
|
||||
- Bulk selection for future batch operations
|
||||
- Real-time updates via LiveView
|
||||
- Bookmarkable URLs with query parameters
|
||||
|
||||
## URL Parameters
|
||||
- `query` - Search query string for full-text search
|
||||
- `sort_field` - Field to sort by (e.g., :first_name, :email, :join_date)
|
||||
- `sort_order` - Sort direction (:asc or :desc)
|
||||
|
||||
## Events
|
||||
- `delete` - Remove a member from the database
|
||||
- `select_member` - Toggle individual member selection
|
||||
- `select_all` - Toggle selection of all visible members
|
||||
|
||||
## Implementation Notes
|
||||
- Search uses PostgreSQL full-text search (plainto_tsquery)
|
||||
- Sort state is synced with URL for bookmarkability
|
||||
- Components communicate via `handle_info` for decoupling
|
||||
"""
|
||||
use MvWeb, :live_view
|
||||
|
||||
@doc """
|
||||
Initializes the LiveView state.
|
||||
|
||||
Sets up initial assigns for page title, search query, sort configuration,
|
||||
and member selection. Actual data loading happens in `handle_params/3`.
|
||||
"""
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
socket =
|
||||
|
|
@ -19,7 +50,14 @@ defmodule MvWeb.MemberLive.Index do
|
|||
# Handle Events
|
||||
# -----------------------------------------------------------------
|
||||
|
||||
# Delete a member
|
||||
@doc """
|
||||
Handles member-related UI events.
|
||||
|
||||
## Supported events:
|
||||
- `"delete"` - Removes a member from the database
|
||||
- `"select_member"` - Toggles individual member selection
|
||||
- `"select_all"` - Toggles selection of all visible members
|
||||
"""
|
||||
@impl true
|
||||
def handle_event("delete", %{"id" => id}, socket) do
|
||||
member = Ash.get!(Mv.Membership.Member, id)
|
||||
|
|
@ -29,7 +67,6 @@ defmodule MvWeb.MemberLive.Index do
|
|||
{:noreply, assign(socket, :members, updated_members)}
|
||||
end
|
||||
|
||||
# Selects one member in the list of members
|
||||
@impl true
|
||||
def handle_event("select_member", %{"id" => id}, socket) do
|
||||
selected =
|
||||
|
|
@ -42,7 +79,6 @@ defmodule MvWeb.MemberLive.Index do
|
|||
{:noreply, assign(socket, :selected_members, selected)}
|
||||
end
|
||||
|
||||
# Selects all members in the list of members
|
||||
@impl true
|
||||
def handle_event("select_all", _params, socket) do
|
||||
members = socket.assigns.members
|
||||
|
|
@ -63,57 +99,23 @@ defmodule MvWeb.MemberLive.Index do
|
|||
# Handle Infos from Child Components
|
||||
# -----------------------------------------------------------------
|
||||
|
||||
# Sorts the list of members according to a field, when you click on the column header
|
||||
@doc """
|
||||
Handles messages from child components.
|
||||
|
||||
## Supported messages:
|
||||
- `{:sort, field}` - Sort event from SortHeaderComponent. Updates sort field/order and syncs URL
|
||||
- `{:search_changed, query}` - Search event from SearchBarComponent. Filters members and syncs URL
|
||||
"""
|
||||
@impl true
|
||||
def handle_info({:sort, field_str}, socket) do
|
||||
field = String.to_existing_atom(field_str)
|
||||
old_field = socket.assigns.sort_field
|
||||
{new_field, new_order} = determine_new_sort(field, socket)
|
||||
|
||||
{new_order, new_field} =
|
||||
if socket.assigns.sort_field == field do
|
||||
{toggle_order(socket.assigns.sort_order), field}
|
||||
else
|
||||
{:asc, field}
|
||||
end
|
||||
|
||||
active_id = :"sort_#{new_field}"
|
||||
old_id = :"sort_#{old_field}"
|
||||
|
||||
# Update the new SortHeader
|
||||
send_update(MvWeb.Components.SortHeaderComponent,
|
||||
id: active_id,
|
||||
sort_field: new_field,
|
||||
sort_order: new_order
|
||||
)
|
||||
|
||||
# Reset the current SortHeader
|
||||
send_update(MvWeb.Components.SortHeaderComponent,
|
||||
id: old_id,
|
||||
sort_field: new_field,
|
||||
sort_order: new_order
|
||||
)
|
||||
|
||||
existing_search_query = socket.assigns.query
|
||||
|
||||
# Build the URL with queries
|
||||
query_params = %{
|
||||
"query" => existing_search_query,
|
||||
"sort_field" => Atom.to_string(new_field),
|
||||
"sort_order" => Atom.to_string(new_order)
|
||||
}
|
||||
|
||||
# Set the new path with params
|
||||
new_path = ~p"/members?#{query_params}"
|
||||
|
||||
# Push the new URL
|
||||
{:noreply,
|
||||
push_patch(socket,
|
||||
to: new_path,
|
||||
replace: true
|
||||
)}
|
||||
socket
|
||||
|> update_sort_components(socket.assigns.sort_field, new_field, new_order)
|
||||
|> push_sort_url(new_field, new_order)
|
||||
end
|
||||
|
||||
# Function to handle search
|
||||
@impl true
|
||||
def handle_info({:search_changed, q}, socket) do
|
||||
socket = load_members(socket, q)
|
||||
|
|
@ -142,6 +144,13 @@ defmodule MvWeb.MemberLive.Index do
|
|||
# -----------------------------------------------------------------
|
||||
# Handle Params from the URL
|
||||
# -----------------------------------------------------------------
|
||||
@doc """
|
||||
Handles URL parameter changes.
|
||||
|
||||
Parses query parameters for search query, sort field, and sort order,
|
||||
then loads members accordingly. This enables bookmarkable URLs and
|
||||
browser back/forward navigation.
|
||||
"""
|
||||
@impl true
|
||||
def handle_params(params, _url, socket) do
|
||||
socket =
|
||||
|
|
@ -156,6 +165,55 @@ defmodule MvWeb.MemberLive.Index do
|
|||
# -------------------------------------------------------------
|
||||
# FUNCTIONS
|
||||
# -------------------------------------------------------------
|
||||
|
||||
# Determines new sort field and order based on current state
|
||||
defp determine_new_sort(field, socket) do
|
||||
if socket.assigns.sort_field == field do
|
||||
{field, toggle_order(socket.assigns.sort_order)}
|
||||
else
|
||||
{field, :asc}
|
||||
end
|
||||
end
|
||||
|
||||
# Updates both the active and old SortHeader components
|
||||
defp update_sort_components(socket, old_field, new_field, new_order) do
|
||||
active_id = :"sort_#{new_field}"
|
||||
old_id = :"sort_#{old_field}"
|
||||
|
||||
# Update the new SortHeader
|
||||
send_update(MvWeb.Components.SortHeaderComponent,
|
||||
id: active_id,
|
||||
sort_field: new_field,
|
||||
sort_order: new_order
|
||||
)
|
||||
|
||||
# Reset the current SortHeader
|
||||
send_update(MvWeb.Components.SortHeaderComponent,
|
||||
id: old_id,
|
||||
sort_field: new_field,
|
||||
sort_order: new_order
|
||||
)
|
||||
|
||||
socket
|
||||
end
|
||||
|
||||
# Builds sort URL and pushes navigation patch
|
||||
defp push_sort_url(socket, field, order) do
|
||||
query_params = %{
|
||||
"query" => socket.assigns.query,
|
||||
"sort_field" => Atom.to_string(field),
|
||||
"sort_order" => Atom.to_string(order)
|
||||
}
|
||||
|
||||
new_path = ~p"/members?#{query_params}"
|
||||
|
||||
{:noreply,
|
||||
push_patch(socket,
|
||||
to: new_path,
|
||||
replace: true
|
||||
)}
|
||||
end
|
||||
|
||||
# Load members eg based on a query for sorting
|
||||
defp load_members(socket, search_query) do
|
||||
query =
|
||||
|
|
|
|||
|
|
@ -1,4 +1,26 @@
|
|||
defmodule MvWeb.MemberLive.Show do
|
||||
@moduledoc """
|
||||
LiveView for displaying a single member's details.
|
||||
|
||||
## Features
|
||||
- Display all member information (personal, contact, address)
|
||||
- Show linked user account (if exists)
|
||||
- Display custom properties
|
||||
- Navigate to edit form
|
||||
- Return to member list
|
||||
|
||||
## Displayed Information
|
||||
- Basic: name, email, dates (birth, join, exit)
|
||||
- Contact: phone number
|
||||
- Address: street, house number, postal code, city
|
||||
- Status: paid flag
|
||||
- Relationships: linked user account
|
||||
- Custom: dynamic properties from PropertyTypes
|
||||
|
||||
## Navigation
|
||||
- Back to member list
|
||||
- Edit member (with return_to parameter for back navigation)
|
||||
"""
|
||||
use MvWeb, :live_view
|
||||
import Ash.Query
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,35 @@
|
|||
defmodule MvWeb.PropertyLive.Form do
|
||||
@moduledoc """
|
||||
LiveView form for creating and editing properties.
|
||||
|
||||
## Features
|
||||
- Create new properties with member and type selection
|
||||
- Edit existing property values
|
||||
- Value input adapts to property type (string, integer, boolean, date, email)
|
||||
- Real-time validation
|
||||
|
||||
## Form Fields
|
||||
**Required:**
|
||||
- member - Select which member owns this property
|
||||
- property_type - Select the type (defines value type)
|
||||
- value - The actual value (input type depends on property type)
|
||||
|
||||
## Value Types
|
||||
The form dynamically renders appropriate inputs based on property type:
|
||||
- String: text input
|
||||
- Integer: number input
|
||||
- Boolean: checkbox
|
||||
- Date: date picker
|
||||
- Email: email input with validation
|
||||
|
||||
## Events
|
||||
- `validate` - Real-time form validation
|
||||
- `save` - Submit form (create or update property)
|
||||
|
||||
## Note
|
||||
Properties are typically managed through the member edit form,
|
||||
not through this standalone form.
|
||||
"""
|
||||
use MvWeb, :live_view
|
||||
|
||||
@impl true
|
||||
|
|
|
|||
|
|
@ -1,4 +1,26 @@
|
|||
defmodule MvWeb.PropertyLive.Index do
|
||||
@moduledoc """
|
||||
LiveView for displaying and managing properties.
|
||||
|
||||
## Features
|
||||
- List all properties with their values and types
|
||||
- Show which member each property belongs to
|
||||
- Display property type information
|
||||
- Navigate to property details and edit forms
|
||||
- Delete properties
|
||||
|
||||
## Relationships
|
||||
Each property is linked to:
|
||||
- A member (the property owner)
|
||||
- A property type (defining value type and behavior)
|
||||
|
||||
## Events
|
||||
- `delete` - Remove a property from the database
|
||||
|
||||
## Note
|
||||
Properties are typically managed through the member edit form.
|
||||
This view provides a global overview of all properties.
|
||||
"""
|
||||
use MvWeb, :live_view
|
||||
|
||||
@impl true
|
||||
|
|
|
|||
|
|
@ -1,4 +1,24 @@
|
|||
defmodule MvWeb.PropertyLive.Show do
|
||||
@moduledoc """
|
||||
LiveView for displaying a single property's details.
|
||||
|
||||
## Features
|
||||
- Display property value and type
|
||||
- Show linked member
|
||||
- Show property type definition
|
||||
- Navigate to edit form
|
||||
- Return to property list
|
||||
|
||||
## Displayed Information
|
||||
- Property value (formatted based on type)
|
||||
- Property type name and description
|
||||
- Member information (who owns this property)
|
||||
- Property metadata (ID, timestamps if added)
|
||||
|
||||
## Navigation
|
||||
- Back to property list
|
||||
- Edit property
|
||||
"""
|
||||
use MvWeb, :live_view
|
||||
|
||||
@impl true
|
||||
|
|
|
|||
|
|
@ -1,4 +1,38 @@
|
|||
defmodule MvWeb.PropertyTypeLive.Form do
|
||||
@moduledoc """
|
||||
LiveView form for creating and editing property types (admin).
|
||||
|
||||
## Features
|
||||
- Create new property type definitions
|
||||
- Edit existing property types
|
||||
- Select value type from supported types
|
||||
- Set immutable and required flags
|
||||
- Real-time validation
|
||||
|
||||
## Form Fields
|
||||
**Required:**
|
||||
- name - Unique identifier (e.g., "phone_mobile", "emergency_contact")
|
||||
- value_type - Data type (:string, :integer, :boolean, :date, :email)
|
||||
|
||||
**Optional:**
|
||||
- description - Human-readable explanation
|
||||
- immutable - If true, values cannot be changed after creation (default: false)
|
||||
- required - If true, all members must have this property (default: false)
|
||||
|
||||
## Value Type Selection
|
||||
- `:string` - Text data (unlimited length)
|
||||
- `:integer` - Numeric data
|
||||
- `:boolean` - True/false flags
|
||||
- `:date` - Date values
|
||||
- `:email` - Validated email addresses
|
||||
|
||||
## Events
|
||||
- `validate` - Real-time form validation
|
||||
- `save` - Submit form (create or update property type)
|
||||
|
||||
## Security
|
||||
Property type management is restricted to admin users.
|
||||
"""
|
||||
use MvWeb, :live_view
|
||||
|
||||
@impl true
|
||||
|
|
|
|||
|
|
@ -1,4 +1,28 @@
|
|||
defmodule MvWeb.PropertyTypeLive.Index do
|
||||
@moduledoc """
|
||||
LiveView for managing property type definitions (admin).
|
||||
|
||||
## Features
|
||||
- List all property types
|
||||
- Display type information (name, value type, description)
|
||||
- Show immutable and required flags
|
||||
- Create new property types
|
||||
- Edit existing property types
|
||||
- Delete property types (if no properties use them)
|
||||
|
||||
## Displayed Information
|
||||
- Name: Unique identifier for the property type
|
||||
- Value type: Data type constraint (string, integer, boolean, date, email)
|
||||
- Description: Human-readable explanation
|
||||
- Immutable: Whether property values can be changed after creation
|
||||
- Required: Whether all members must have this property (future feature)
|
||||
|
||||
## Events
|
||||
- `delete` - Remove a property type (only if no properties exist)
|
||||
|
||||
## Security
|
||||
Property type management is restricted to admin users.
|
||||
"""
|
||||
use MvWeb, :live_view
|
||||
|
||||
@impl true
|
||||
|
|
|
|||
|
|
@ -1,4 +1,27 @@
|
|||
defmodule MvWeb.PropertyTypeLive.Show do
|
||||
@moduledoc """
|
||||
LiveView for displaying a single property type's details (admin).
|
||||
|
||||
## Features
|
||||
- Display property type definition
|
||||
- Show all attributes (name, value type, description, flags)
|
||||
- Navigate to edit form
|
||||
- Return to property type list
|
||||
|
||||
## Displayed Information
|
||||
- Name: Unique identifier
|
||||
- Value type: Data type constraint
|
||||
- Description: Optional explanation
|
||||
- Immutable flag: Whether values can be changed
|
||||
- Required flag: Whether all members need this property
|
||||
|
||||
## Navigation
|
||||
- Back to property type list
|
||||
- Edit property type
|
||||
|
||||
## Security
|
||||
Property type details are restricted to admin users.
|
||||
"""
|
||||
use MvWeb, :live_view
|
||||
|
||||
@impl true
|
||||
|
|
|
|||
|
|
@ -1,4 +1,36 @@
|
|||
defmodule MvWeb.UserLive.Form do
|
||||
@moduledoc """
|
||||
LiveView form for creating and editing users.
|
||||
|
||||
## Features
|
||||
- Create new users with email
|
||||
- Edit existing user details
|
||||
- Optional password setting (checkbox to toggle)
|
||||
- Link/unlink member accounts
|
||||
- Email synchronization with linked members
|
||||
|
||||
## Form Fields
|
||||
**Required:**
|
||||
- email
|
||||
|
||||
**Optional:**
|
||||
- password (for password authentication strategy)
|
||||
- linked member (select from existing members)
|
||||
|
||||
## Password Management
|
||||
- New users: Can optionally set password with confirmation
|
||||
- Existing users: Can change password (no confirmation required, admin action)
|
||||
- Checkbox toggles password section visibility
|
||||
|
||||
## Member Linking
|
||||
Users can be linked to existing member accounts. When linked, emails are
|
||||
synchronized bidirectionally with User.email as the source of truth.
|
||||
|
||||
## Events
|
||||
- `validate` - Real-time form validation
|
||||
- `save` - Submit form (create or update user)
|
||||
- `toggle_password_section` - Show/hide password fields
|
||||
"""
|
||||
use MvWeb, :live_view
|
||||
|
||||
@impl true
|
||||
|
|
|
|||
|
|
@ -1,4 +1,25 @@
|
|||
defmodule MvWeb.UserLive.Index do
|
||||
@moduledoc """
|
||||
LiveView for displaying and managing the user list.
|
||||
|
||||
## Features
|
||||
- List all users with email and linked member
|
||||
- Sort users by email (default)
|
||||
- Delete users
|
||||
- Navigate to user details and edit forms
|
||||
- Bulk selection for future batch operations
|
||||
|
||||
## Relationships
|
||||
Displays linked member information when a user is connected to a member account.
|
||||
|
||||
## Events
|
||||
- `delete` - Remove a user from the database
|
||||
- `select_user` - Toggle individual user selection
|
||||
- `select_all` - Toggle selection of all visible users
|
||||
|
||||
## Security
|
||||
User deletion requires admin permissions (enforced by Ash policies).
|
||||
"""
|
||||
use MvWeb, :live_view
|
||||
import MvWeb.TableComponents
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,29 @@
|
|||
defmodule MvWeb.UserLive.Show do
|
||||
@moduledoc """
|
||||
LiveView for displaying a single user's details.
|
||||
|
||||
## Features
|
||||
- Display user information (email, OIDC ID)
|
||||
- Show authentication methods (password, OIDC)
|
||||
- Display linked member account (if exists)
|
||||
- Navigate to edit form
|
||||
- Return to user list
|
||||
|
||||
## Displayed Information
|
||||
- Email address
|
||||
- OIDC ID (if authenticated via OIDC)
|
||||
- Password authentication status
|
||||
- Linked member (name and email)
|
||||
|
||||
## Authentication Status
|
||||
Shows which authentication methods are enabled for the user:
|
||||
- Password authentication (has hashed_password)
|
||||
- OIDC authentication (has oidc_id)
|
||||
|
||||
## Navigation
|
||||
- Back to user list
|
||||
- Edit user (with return_to parameter for back navigation)
|
||||
"""
|
||||
use MvWeb, :live_view
|
||||
|
||||
@impl true
|
||||
|
|
|
|||
|
|
@ -1,4 +1,16 @@
|
|||
defmodule MvWeb.LiveHelpers do
|
||||
@moduledoc """
|
||||
Shared LiveView lifecycle hooks and helper functions.
|
||||
|
||||
## on_mount Hooks
|
||||
- `:default` - Sets the user's locale from session (defaults to "de")
|
||||
|
||||
## Usage
|
||||
Add to LiveView modules via:
|
||||
```elixir
|
||||
on_mount {MvWeb.LiveHelpers, :default}
|
||||
```
|
||||
"""
|
||||
def on_mount(:default, _params, session, socket) do
|
||||
locale = session["locale"] || "de"
|
||||
Gettext.put_locale(locale)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue