diff --git a/lib/membership/email.ex b/lib/membership/email.ex index c611742..dccec21 100644 --- a/lib/membership/email.ex +++ b/lib/membership/email.ex @@ -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 diff --git a/lib/membership/member.ex b/lib/membership/member.ex index d0b8124..26c876f 100644 --- a/lib/membership/member.ex +++ b/lib/membership/member.ex @@ -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 diff --git a/lib/membership/property.ex b/lib/membership/property.ex index de096ca..231b264 100644 --- a/lib/membership/property.ex +++ b/lib/membership/property.ex @@ -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 diff --git a/lib/membership/property_type.ex b/lib/membership/property_type.ex index 7444c13..6569d1b 100644 --- a/lib/membership/property_type.ex +++ b/lib/membership/property_type.ex @@ -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