mitgliederverwaltung/docs/groups-architecture.md
Simon 8e9fbe76cf
Some checks failed
continuous-integration/drone/push Build is failing
docs: add testing philosophy to coding guideline
and update groups architecture docs #371
2026-01-27 15:23:40 +01:00

33 KiB

Groups - Technical Architecture

Project: Mila - Membership Management System
Feature: Groups Management
Version: 1.0
Last Updated: 2025-01-XX
Status: Architecture Design - Ready for Implementation


Purpose

This document defines the technical architecture for the Groups feature. It focuses on architectural decisions, patterns, module structure, and integration points without concrete implementation details.

Related Documents:


Table of Contents

  1. Architecture Principles
  2. Domain Structure
  3. Data Architecture
  4. Business Logic Architecture
  5. UI/UX Architecture
  6. Integration Points
  7. Authorization
  8. Performance Considerations
  9. Future Extensibility
  10. Implementation Phases

Architecture Principles

Core Design Decisions

  1. Many-to-Many Relationship:

    • Members can belong to multiple groups
    • Groups can contain multiple members
    • Implemented via join table (member_groups) as separate Ash resource
  2. Flat Structure (MVP):

    • Groups are initially flat (no hierarchy)
    • Architecture designed to allow hierarchical extension later
    • No parent/child relationships in MVP
  3. Minimal Attributes (MVP):

    • name, description, and slug in initial version
    • slug is automatically generated from name (immutable, URL-friendly)
    • Extensible for future attributes (dates, status, etc.)
  4. Cascade Deletion:

    • Deleting a group removes all member-group associations
    • Members themselves are not deleted (CASCADE on join table only)
    • Requires explicit confirmation with group name input
  5. Search Integration:

    • Groups searchable within member search (not separate search)
    • Group names included in member search vector for full-text search

Domain Structure

Ash Domain: Mv.Membership

Purpose: Groups are part of the Membership domain, alongside Members and CustomFields

New Resources:

  • Group - Group definitions (name, description, slug)
  • MemberGroup - Join table for many-to-many relationship between Members and Groups

Extended Resources:

  • Member - Extended with has_many :groups relationship (through MemberGroup)

Module Organization

lib/
├── membership/
│   ├── membership.ex              # Domain definition (extended)
│   ├── group.ex                   # Group resource
│   ├── member_group.ex            # MemberGroup join table resource
│   └── member.ex                  # Extended with groups relationship
├── mv_web/
│   └── live/
│       ├── group_live/
│       │   ├── index.ex           # Groups management page
│       │   ├── form.ex            # Create/edit group form
│       │   └── show.ex            # Group detail view
│       └── member_live/
│           ├── index.ex            # Extended with group filtering/sorting
│           └── show.ex            # Extended with group display
└── mv/
    └── membership/
        └── group/                 # Future: Group-specific business logic
            └── helpers.ex        # Group-related helper functions

Data Architecture

Database Schema

groups Table

Attributes:

  • id - UUID v7 primary key
  • name - Unique group name (required, max 100 chars)
  • slug - URL-friendly identifier (required, max 100 chars, auto-generated from name)
  • description - Optional description (max 500 chars)
  • inserted_at / updated_at - Timestamps

Constraints:

  • name must be unique (case-insensitive, using LOWER(name))
  • slug must be unique (case-sensitive, exact match)
  • name cannot be null
  • slug cannot be null
  • name max length: 100 characters
  • slug max length: 100 characters
  • description max length: 500 characters

member_groups Table (Join Table)

Attributes:

  • id - UUID v7 primary key
  • member_id - Foreign key to members (CASCADE delete)
  • group_id - Foreign key to groups (CASCADE delete)
  • inserted_at / updated_at - Timestamps

Constraints:

  • Unique constraint on (member_id, group_id) - prevents duplicate memberships
  • CASCADE delete: Removing member removes all group associations
  • CASCADE delete: Removing group removes all member associations

Indexes:

  • Index on member_id for efficient member → groups queries
  • Index on group_id for efficient group → members queries

Ash Resources

Mv.Membership.Group

Relationships:

  • has_many :member_groups - Relationship to MemberGroup join table
  • many_to_many :members - Relationship to Members through MemberGroup

Calculations:

  • member_count - Integer calculation counting associated members

Actions:

  • create - Create new group (auto-generates slug from name)
  • read - List/search groups (can query by slug via identity)
  • update - Update group name/description (slug remains unchanged)
  • destroy - Delete group (with confirmation)

Validations:

  • name required, unique (case-insensitive), max 100 chars
  • slug required, unique (case-sensitive), max 100 chars, auto-generated, immutable
  • description optional, max 500 chars

Identities:

  • unique_slug - Unique identity on slug for efficient lookups

Mv.Membership.MemberGroup

Relationships:

  • belongs_to :member - Relationship to Member
  • belongs_to :group - Relationship to Group

Actions:

  • create - Add member to group
  • read - Query member-group associations
  • destroy - Remove member from group

Validations:

  • Unique constraint on (member_id, group_id)

Mv.Membership.Member (Extended)

New Relationships:

  • has_many :member_groups - Relationship to MemberGroup join table
  • many_to_many :groups - Relationship to Groups through MemberGroup

New Actions:

  • add_to_groups - Add member to one or more groups
  • remove_from_groups - Remove member from one or more groups

Business Logic Architecture

Group Management

Create Group:

  • Validate name uniqueness
  • Automatically generate slug from name (using GenerateSlug change, same pattern as CustomFields)
  • Validate slug uniqueness
  • Return created group

Update Group:

  • Validate name uniqueness (if name changed)
  • Update description
  • Slug remains unchanged (immutable after creation)
  • Return updated group

Delete Group:

  • Check if group has members (for warning display)
  • Require explicit confirmation (group name input)
  • Cascade delete all member_groups associations
  • Group itself deleted

Member-Group Association

Add Member to Group:

  • Validate member exists
  • Validate group exists
  • Check for duplicate association
  • Create MemberGroup record

Remove Member from Group:

  • Find MemberGroup record
  • Delete association
  • Member and group remain intact

Bulk Operations:

  • Add member to multiple groups in single transaction
  • Remove member from multiple groups in single transaction

Search Integration

Member Search Enhancement:

  • Include group names in member search vector
  • When searching for member, also search in associated group names
  • Example: Searching for a group name finds all members in groups with that name

Implementation:

  • Extend member.search_vector trigger to include group names
  • Update trigger on member_groups changes
  • Use PostgreSQL tsvector for full-text search

UI/UX Architecture

Groups Management Page (/groups)

Route: /groups - Groups management index page

Features:

  • List all groups in table
  • Create new group button
  • Edit group (inline or modal)
  • Delete group with confirmation modal
  • Show member count per group

Table Columns:

  • Name (sortable, searchable)
  • Description
  • Member Count
  • Actions (Edit, Delete)

Delete Confirmation Modal:

  • Warning: "X members are in this group"
  • Confirmation: "All member-group associations will be permanently deleted"
  • Input field: Enter group name to confirm
  • Delete button disabled until name matches
  • Cancel button

Member Overview Integration

New Column: "Groups"

  • Display group badges for each member
  • Badge shows group name
  • Multiple badges if member in multiple groups
  • (Optional) Click badge to filter by that group (enhanced UX, can be added later)

Filtering:

  • Dropdown/select to filter by group
  • "All groups" option (default)
  • Filter persists in URL query params
  • Works with existing search/sort

Sorting:

  • Sort by group name (members with groups first, then alphabetically)
  • Sort by number of groups (members with most groups first)

Search:

  • Group names included in member search
  • Searching group name shows all members in that group

Member Detail View Integration

New Section: "Groups"

  • List all groups member belongs to
  • Display as badges or list
  • Add/remove groups inline
  • Link to group detail page

Group Detail View (/groups/:id)

Route: /groups/:id - Group detail page (uses UUID, slug can be used for future /groups/:slug routes)

Features:

  • Display group name and description
  • List all members in group
  • Link to member detail pages
  • Edit group button
  • Delete group button (with confirmation)

Note: Currently uses UUID for routing. Slug is available for future URL-friendly routes (/groups/:slug).

Accessibility (A11y) Considerations

Requirements:

  • All UI elements must be keyboard accessible
  • Screen readers must be able to navigate and understand the interface
  • ARIA labels and roles must be properly set

Group Badges in Member Overview:

  • Badges must have role="status" and appropriate aria-label attributes
  • Badge title should indicate group membership

Clickable Group Badge (for filtering) - Optional:

Note: This is an optional enhancement. The dropdown filter provides the same functionality. The clickable badge improves UX by showing the active filter visually and allowing quick removal.

Estimated effort: 1.5-2.5 hours

  • Clickable badges must be proper button elements with type="button"
  • Must include aria-label describing the filter action
  • Icon for removal should have aria-hidden="true"

Group Filter Dropdown:

  • Select element must have appropriate id, name, and aria-label attributes
  • Options should clearly indicate selected state

Screen Reader Announcements:

  • Use role="status" with aria-live="polite" for dynamic content
  • Announce filter changes and member count updates

Delete Confirmation Modal:

  • Modal must use proper role="dialog" with aria-labelledby and aria-describedby
  • Warning messages must be clearly associated with the modal description
  • Form inputs must be properly labeled

Keyboard Navigation:

  • All interactive elements (buttons, links, form inputs) must be focusable via Tab key
  • Modal dialogs must trap focus (Tab key cycles within modal)
  • Escape key closes modals
  • Enter/Space activates buttons when focused

Integration Points

Member Search Vector

Trigger Update:

  • When member_groups record created/deleted
  • Update members.search_vector to include group names
  • Use PostgreSQL trigger for automatic updates

Search Query:

  • Extend existing fuzzy_search to include group names
  • Group names added with weight 'B' (same as city, etc.)

Member Form

Future Enhancement:

  • Add groups selection in member form
  • Multi-select dropdown for groups
  • Add/remove groups during member creation/edit

Authorization Integration

Current (MVP):

  • Only admins can manage groups
  • Uses existing Mv.Authorization.Checks.HasPermission
  • Permission: groups resource with :all scope

Future:

  • Group-specific permissions
  • Role-based group management
  • Member-level group assignment permissions

Authorization

Permission Model (MVP)

Resource: groups

Actions:

  • read - View groups (all users with member read permission)
  • create - Create groups (admin only)
  • update - Edit groups (admin only)
  • destroy - Delete groups (admin only)

Scopes:

  • :all - All groups (for all permission sets that have read access)

Permission Sets Update

Admin Permission Set:

  • read action on Group resource with :all scope - granted
  • create action on Group resource with :all scope - granted
  • update action on Group resource with :all scope - granted
  • destroy action on Group resource with :all scope - granted

Read-Only Permission Set:

  • read action on Group resource with :all scope - granted

Normal User Permission Set:

  • read action on Group resource with :all scope - granted

Own Data Permission Set:

  • read action on Group resource with :all scope - granted

Note: All permission sets use :all scope for groups. Groups are considered public information that all users with member read permission can view. Only admins can manage (create/update/destroy) groups.

Member-Group Association Permissions

Current (MVP):

  • Adding/removing members from groups requires group update permission
  • Managed through group edit interface

Future:

  • Separate permission for member-group management
  • Member-level permissions for self-assignment

Performance Considerations

Database Indexes

Critical Indexes:

  • groups.name - For uniqueness and search (case-insensitive via LOWER)
  • groups.slug - For uniqueness and efficient lookups (unique index)
  • member_groups.member_id - For member → groups queries
  • member_groups.group_id - For group → members queries
  • Composite index on (member_id, group_id) - For uniqueness check

Query Optimization

Member Overview:

  • Load groups with members in single query using query preloading
  • Preload only necessary group attributes (id, name, slug) to minimize data transfer
  • Filter groups at database level when filtering by group

N+1 Query Prevention:

  • Always preload groups relationship when querying members
  • Never access member groups without preloading (would trigger N+1 queries)
  • Use query preloading mechanisms to load all required relationships in a single query

Performance Threshold:

  • With proper preloading: Works efficiently up to 100 members (MVP scope)
  • For larger datasets (>100 members), consider:
    • Pagination (limit number of members loaded)
    • Lazy loading of groups (only load when groups column is visible)
    • Database-level aggregation for group counts

Group Detail:

  • Paginate member list for large groups (>50 members)
  • Load member count via calculation (not separate query)
  • Use Ash.Query.load for member details when displaying

Search Performance

Search Vector:

  • Group names included in search_vector (tsvector)
  • GIN index on search_vector for fast full-text search
  • Trigger updates on member_groups changes

Filtering:

  • Use database-level filtering (not in-memory)
  • Leverage indexes for group filtering

Future Extensibility

Hierarchical Groups

Design for Future:

  • Add parent_group_id to groups table (nullable)
  • Add parent_group relationship (self-referential)
  • Add validation to prevent circular references
  • Add calculation for path (e.g., "Parent > Child > Grandchild")

Migration Path:

  • Add column with NULL default (all groups initially root-level)
  • Add foreign key constraint
  • Add validation logic
  • Update UI to show hierarchy

Group Attributes

Future Attributes:

  • created_at / founded_date - Group creation date
  • dissolved_at - Group dissolution date
  • status - Active/inactive/suspended
  • color - UI color for badges
  • icon - Icon identifier

Migration Path:

  • Add nullable columns
  • Set defaults for existing groups
  • Update UI to display new attributes

Roles/Positions in Groups

Future Feature:

  • Add member_group_roles table
  • Link MemberGroup to Role (e.g., "Leiter", "Mitglied")
  • Extend MemberGroup with role_id foreign key
  • Display role in member detail and group detail views

Group Permissions

Future Feature:

  • Group-specific permission sets
  • Role-based group access
  • Member-level group management permissions

Feature Breakdown: Functional Units and MVP

Strategy: Vertical Slicing

The Groups feature is divided into functionally complete, vertical units. Each unit delivers a complete, usable functional area that can be independently tested and delivered.

MVP Definition

Minimal Viable Product (MVP): The MVP includes the core functionality necessary to manage groups and assign them to members:

  1. Create groups (Name + Description + Slug)
  2. Edit groups
  3. Delete groups (with confirmation)
  4. Assign members to groups
  5. Remove members from groups
  6. Display groups in member overview
  7. Filter by groups
  8. Sort by groups
  9. Display groups in member detail
  10. Groups in member search (automatically via search_vector)

Not in MVP:

  • Hierarchical groups
  • Roles/positions in groups
  • Extended group attributes (dates, status, etc.)
  • Group-specific permissions

Functional Units (Vertical Slices)

Unit 1: Group Management (Backend)

Functional Scope: Administrators can manage groups in the system

Scope:

  • Group resource (Name, Description, Slug)
  • CRUD operations for groups
  • Validations (unique name, unique slug, length limits)
  • Automatic slug generation from name
  • Delete logic with cascade behavior

Deliverable: Groups can be created, edited, and deleted via API

Dependencies: None

Estimation: 4-5h


Unit 2: Member-Group Assignment (Backend)

Functional Scope: Members can be assigned to groups

Scope:

  • MemberGroup join table
  • Many-to-Many relationship
  • Add/Remove member-group associations
  • Cascade delete behavior

Deliverable: Members can be assigned to and removed from groups

Dependencies: Unit 1 (Groups must exist)

Estimation: 2-3h (can be combined with Unit 1)


Unit 3: Group Management UI

Functional Scope: Administrators can manage groups via web interface

Scope:

  • Groups overview page (/groups)
  • Group form (Create/Edit)
  • Group detail page (member list)
  • Delete confirmation modal (with name input)

Deliverable: Complete group management via UI possible

Dependencies: Unit 1 + 2 (Backend must be functional)

Estimation: 3-4h


Unit 4: Groups in Member Overview

Functional Scope: Groups are displayed in member overview and can be filtered/sorted

Scope:

  • "Groups" column with badges
  • Group filter dropdown
  • Sorting by groups
  • URL parameter persistence

Deliverable: Groups visible, filterable, and sortable in member overview

Dependencies: Unit 1 + 2 (Groups and assignments must exist)

Estimation: 2-3h


Unit 5: Groups in Member Detail

Functional Scope: Groups are displayed in member detail view

Scope:

  • "Groups" section in Member Show
  • Badge display
  • Links to group detail pages

Deliverable: Groups visible in member detail

Dependencies: Unit 3 (Group detail page must exist)

Estimation: 1-2h


Functional Scope: Group names are searchable in member search

Scope:

  • Search Vector Update (Trigger)
  • Fuzzy Search extension
  • Test search functionality

Deliverable: Searching for group names finds associated members

Dependencies: Unit 1 + 2 (Groups and assignments must exist)

Estimation: 2h


Unit 7: Permissions

Functional Scope: Only administrators can manage groups

Scope:

  • Add groups to permission sets
  • Implement authorization policies
  • UI permission checks

Deliverable: Permissions correctly implemented

Dependencies: All previous units (Feature must be functional)

Estimation: 1-2h


MVP Composition

MVP consists of:

  • Unit 1: Group Management (Backend)
  • Unit 2: Member-Group Assignment (Backend)
  • Unit 3: Group Management UI
  • Unit 4: Groups in Member Overview
  • Unit 5: Groups in Member Detail
  • Unit 6: Groups in Member Search (automatically via search_vector)
  • Unit 7: Permissions

Total MVP Estimation: 13-15h

Implementation Order

Recommended Order:

  1. Phase 1: Backend Foundation (Unit 1 + 2)

    • Group resource
    • MemberGroup join table
    • CRUD operations
    • Result: Groups can be managed via API
  2. Phase 2: Management UI (Unit 3)

    • Groups overview
    • Group form
    • Group detail
    • Result: Groups can be managed via UI
  3. Phase 3: Member Integration (Unit 4 + 5)

    • Groups in overview
    • Groups in detail
    • Result: Groups visible in member views
  4. Phase 4: Search Integration (Unit 6)

    • Search Vector Update
    • Result: Groups searchable
  5. Phase 5: Permissions (Unit 7)

    • Permission Sets
    • Policies
    • Result: Permissions correct

Issue Structure

Each functional unit can be implemented as a separate issue:

  • Issue 1: Group Resource & Database Schema (Unit 1 + 2)
  • Issue 2: Group Management UI (Unit 3)
  • Issue 3: Groups in Member Overview (Unit 4)
  • Issue 4: Groups in Member Detail (Unit 5)
  • Issue 5: Groups in Member Search (Unit 6)
  • Issue 6: Permissions (Unit 7)

Alternative: Issues 3 and 4 can be combined, as they both concern the display of groups.


Implementation Phases

Phase 1: MVP Core (Foundation)

Goal: Basic group management and member assignment

Tasks:

  1. Create Group resource (name, description, slug)
  2. Implement slug generation (reuse GenerateSlug change from CustomFields)
  3. Create MemberGroup join table resource
  4. Extend Member with groups relationship
  5. Database migrations (including slug column and unique index)
  6. Basic CRUD actions for groups
  7. Add/remove members from groups (via group management)

Deliverables:

  • Groups can be created, edited, deleted
  • Members can be added/removed from groups
  • Basic validation and constraints

Estimation: 4-5h

Phase 2: UI - Groups Management

Goal: Complete groups management interface

Tasks:

  1. Groups index page (/groups)
  2. Group form (create/edit)
  3. Group show page (list members)
  4. Delete confirmation modal (with name input)
  5. Member count display

Deliverables:

  • Full groups management UI
  • Delete confirmation workflow
  • Group detail view

Estimation: 3-4h

Phase 3: Member Overview Integration

Goal: Display and filter groups in member overview

Tasks:

  1. Add "Groups" column to member overview table
  2. Display group badges
  3. Group filter dropdown
  4. Group sorting
  5. URL query param persistence
  6. (Optional) Clickable group badges for filtering (enhanced UX)

Deliverables:

  • Groups visible in member overview
  • Filter by group (via dropdown)
  • Sort by group
  • (Optional) Clickable badges for quick filtering

Estimation: 2-3h

Phase 4: Member Detail Integration

Goal: Display groups in member detail view

Tasks:

  1. Add "Groups" section to member show page
  2. Display group badges
  3. Link to group detail pages

Deliverables:

  • Groups visible in member detail
  • Navigation to group pages

Estimation: 1-2h

Phase 5: Search Integration

Goal: Include groups in member search

Tasks:

  1. Update search_vector trigger to include group names
  2. Extend fuzzy_search to search group names
  3. Test search functionality

Deliverables:

  • Group names searchable in member search
  • Search finds members by group name

Estimation: 2h

Phase 6: Authorization

Goal: Implement permission-based access control

Tasks:

  1. Add groups to permission sets
  2. Implement authorization policies
  3. Test permission enforcement
  4. Update UI to respect permissions

Deliverables:

  • Only admins can manage groups
  • All users can view groups (if they can view members)

Estimation: 1-2h

Total Estimation: 13-18h

Note: This aligns with the issue estimation of 15h.


Issue Breakdown

Issue 1: Groups Resource & Database Schema

Type: Backend
Estimation: 4-5h
Tasks:

  • Create Group resource (with slug attribute and generation)
  • Create MemberGroup join table resource
  • Extend Member resource
  • Database migrations (including slug column)
  • Basic validations (name, slug, description)

Acceptance Criteria:

  • Groups can be created via Ash API
  • Slug is automatically generated from name
  • Slug is unique and immutable
  • Members can be associated with groups
  • Database constraints enforced (unique name, unique slug, foreign keys)

Issue 2: Groups Management UI

Type: Frontend
Estimation: 3-4h
Tasks:

  • Groups index page
  • Group form (create/edit)
  • Group show page
  • Delete confirmation modal

Acceptance Criteria:

  • Groups can be created/edited/deleted via UI
  • Delete requires name confirmation
  • Member count displayed

Issue 3: Member Overview - Groups Integration

Type: Frontend
Estimation: 2-3h
Tasks:

  • Add groups column with badges
  • Group filter dropdown
  • Group sorting
  • URL persistence
  • (Optional) Clickable group badges for filtering

Acceptance Criteria:

  • Groups visible in member overview
  • Can filter by group (via dropdown)
  • Can sort by group
  • Filter persists in URL
  • (Optional) Can filter by clicking group badge

Issue 4: Member Detail - Groups Display

Type: Frontend
Estimation: 1-2h
Tasks:

  • Add groups section to member show
  • Display group badges
  • Link to group pages

Acceptance Criteria:

  • Groups visible in member detail
  • Links to group pages work

Issue 5: Search Integration

Type: Backend
Estimation: 2h
Tasks:

  • Update search vector trigger to include group names
  • Extend fuzzy search to search group names
  • Test search functionality

Acceptance Criteria:

  • Group names searchable in member search (automatic via search_vector)
  • Search finds members by group name
  • Search vector updates automatically when member-group associations change

Note: This is part of MVP as group names are automatically included in full-text search once the search_vector trigger is updated.

Issue 6: Authorization

Type: Backend/Frontend
Estimation: 1-2h
Tasks:

  • Add groups to permission sets
  • Implement policies
  • Test permissions

Acceptance Criteria:

  • Only admins can manage groups
  • All users can view groups (if they can view members)

Testing Strategy

Testing Philosophy

Focus on Business Logic, Not Framework Functionality

We test our business logic and domain-specific behavior, not core framework features. Framework features (Ash validations, Ecto relationships, etc.) are already tested by their respective libraries.

What We Test:

  • Business rules and validations specific to our domain
  • Custom business logic (slug generation, calculations, etc.)
  • Integration between our resources
  • Database-level constraints (unique constraints, foreign keys, CASCADE)
  • Query performance (N+1 prevention)

What We Don't Test:

  • Framework core functionality (Ash validations work, Ecto relationships work, etc.)
  • Standard CRUD operations without custom logic
  • Framework-provided features that are already tested upstream

Unit Tests

Group Resource Tests

File: test/membership/group_test.exs

Test Cases:

  • Create group with valid attributes
  • Return error when name is missing (required validation)
  • Return error when name exceeds 100 characters (length validation)
  • Return error when name is not unique (case-insensitive) - application level validation
  • Allow description to be nil (optional field)
  • Description max length is 500 characters (length validation)
  • Slug is automatically generated from name on create (custom business logic)
  • Slug is immutable (doesn't change when name is updated) - business rule
  • Slug is unique (prevents duplicate slugs from different names) - business rule
  • Slug cannot be empty (rejects name with only special characters) - business rule
  • Update group name and description
  • Prevent duplicate name on update (case-insensitive) - business rule
  • Delete group and all member associations (cascade behavior)
  • Do not delete members themselves (cascade boundary)
  • Member count calculation returns 0 for empty group (custom calculation)
  • Member count calculation returns correct count when members added/removed (custom calculation)

Note: Detailed slug generation tests (Umlauts, truncation, etc.) are covered by the GenerateSlug change tests in custom_field_slug_test.exs, which is reused for groups. We don't duplicate these framework-level tests.

MemberGroup Resource Tests

File: test/membership/member_group_test.exs

Test Cases:

  • Create association between member and group
  • Prevent duplicate associations (unique constraint)
  • Cascade delete when member deleted (database constraint)
  • Cascade delete when group deleted (database constraint)

Database Constraint Tests

File: test/membership/group_database_constraints_test.exs

Test Cases:

  • Database enforces unique name constraint (case-insensitive via LOWER) - DB level
  • Database enforces unique slug constraint (case-sensitive) - DB level
  • Cannot create MemberGroup with non-existent member_id (foreign key constraint)
  • Cannot create MemberGroup with non-existent group_id (foreign key constraint)
  • Deleting member cascades to member_groups (verified at DB level)
  • Deleting group cascades to member_groups (verified at DB level)

Note: These tests verify that constraints are enforced at the database level, not just application level.

Integration Tests

Member-Group Relationships

File: test/membership/group_integration_test.exs

Test Cases:

  • Member can belong to multiple groups (many-to-many relationship)
  • Group can contain multiple members (many-to-many relationship)
  • Preloading groups with members avoids N+1 queries (performance test with query count verification)

File: test/membership/member_groups_relationship_test.exs

Test Cases:

  • Member has many_to_many groups relationship (load with preloading)
  • Load multiple members with groups preloaded (N+1 prevention)
  • Add member to group via Ash API
  • Remove member from group via Ash API
  • Add member to multiple groups in single operation
  • Adding member to same group twice fails (duplicate prevention)
  • Removing member from group they're not in (idempotent, no error)

UI Tests

Groups Management

File: test/mv_web/live/group_live/index_test.exs

Test Cases:

  • List all groups
  • Create new group
  • Delete group with confirmation

Member Overview Integration

File: test/mv_web/live/member_live/index_groups_test.exs

Test Cases:

  • Display group badges
  • Filter members by group

Migration Strategy

Database Migrations

Migration 1: Create groups table

  • Create table with UUID v7 primary key
  • Add name field (required, unique, case-insensitive)
  • Add slug field (required, unique, case-sensitive, auto-generated)
  • Add description field (optional)
  • Add timestamps
  • Create unique index on lowercased name (for name uniqueness)
  • Create unique index on slug (for slug uniqueness and lookups)
  • Create index on lowercased name for search

Note: Slug generation follows the same pattern as CustomFields:

  • Uses Mv.Membership.CustomField.Changes.GenerateSlug (reusable change)
  • Or create Mv.Membership.Group.Changes.GenerateSlug if needed
  • Slug is generated on create, immutable on update

Migration 2: Create member_groups join table

  • Create table with UUID v7 primary key
  • Add member_id and group_id foreign keys
  • Add timestamps
  • Create unique index on (member_id, group_id)
  • Create indexes on member_id and group_id
  • Add foreign key constraints with CASCADE delete

Migration 3: Update search_vector trigger (if needed)

  • Extend trigger to include group names
  • Update trigger function

Code Migration

Ash Resources:

  • Use code generation tools to generate migrations
  • Manually adjust if needed

Domain Updates:

  • Add groups resources to domain
  • Define domain actions

Summary

This architecture provides a solid foundation for the Groups feature while maintaining flexibility for future enhancements. The many-to-many relationship is implemented via a join table, following existing patterns in the codebase. The MVP focuses on core functionality (create, edit, delete groups, assign members) with clear extension points for hierarchical groups, roles, and advanced permissions.

Slug Implementation: Groups include automatic slug generation, following the same pattern as CustomFields. Slugs are:

  • Automatically generated from the name attribute on create
  • Immutable after creation (don't change when name is updated)
  • Unique and URL-friendly
  • Available for future route enhancements (e.g., /groups/:slug instead of /groups/:id)

The implementation reuses the existing GenerateSlug change from CustomFields, ensuring consistency across the codebase.

The implementation is split into 6 manageable issues, totaling approximately 15 hours of work, aligning with the original estimation. Each phase builds on the previous one, allowing for incremental development and testing.