Code documentation and refactoring #201

Merged
moritz merged 6 commits from feature/refactoring into main 2025-11-13 11:21:32 +01:00
28 changed files with 689 additions and 55 deletions

View file

@ -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, []},

View file

@ -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:**

View file

@ -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

View file

@ -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

View file

@ -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]

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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()

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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(

View file

@ -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

View file

@ -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

View file

@ -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 =

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)