# 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:** - [database-schema-readme.md](./database-schema-readme.md) - Database documentation - [roles-and-permissions-architecture.md](./roles-and-permissions-architecture.md) - Authorization system --- ## Table of Contents 1. [Architecture Principles](#architecture-principles) 2. [Domain Structure](#domain-structure) 3. [Data Architecture](#data-architecture) 4. [Business Logic Architecture](#business-logic-architecture) 5. [UI/UX Architecture](#uiux-architecture) 6. [Integration Points](#integration-points) 7. [Authorization](#authorization) 8. [Performance Considerations](#performance-considerations) 9. [Future Extensibility](#future-extensibility) 10. [Implementation Phases](#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 (sorted by name via database query) - Create new group button (navigates to `/groups/new`) - Edit group via separate form page (`/groups/:slug/edit`) - 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" (with proper pluralization) - Confirmation: "All member-group associations will be permanently deleted" - Input field: Enter group name to confirm (with `phx-debounce="200"` for better UX) - Delete button disabled until name matches - Modal remains open on name mismatch (allows user to correct input) - Cancel button - Server-side authorization check in delete event handler (security best practice) ### 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/:slug`) **Route:** `/groups/:slug` - Group detail page (uses slug for URL-friendly routing) **Features:** - Display group name and description - List all members in group - Link to member detail pages - Add members to group (via modal with search/autocomplete) - Remove members from group (via remove button per member) - Edit group button (navigates to `/groups/:slug/edit`) - Delete group button (with confirmation modal) **Add Member Functionality:** - "Add Member" button displayed above member table (only for users with `:update` permission) - Opens modal with member search/autocomplete - Search filters out members already in the group - Selecting a member adds them to the group immediately - Success/error flash messages provide feedback **Remove Member Functionality:** - "Remove" button (icon button) for each member in table (only for users with `:update` permission) - Clicking remove immediately removes member from group (no confirmation dialog) - Success/error flash messages provide feedback **Note:** Uses slug for routing to provide URL-friendly, readable group URLs (e.g., `/groups/board-members`). ### Group Form Pages **Create Form:** `/groups/new` - Separate LiveView page for creating new groups - Form with name and description fields - Slug is auto-generated and not editable - Redirects to `/groups` on success **Edit Form:** `/groups/:slug/edit` - Separate LiveView page for editing existing groups - Form pre-populated with current group data - Slug is immutable (not displayed in form) - Redirects to `/groups/:slug` on success - `mount/3` performs authorization check, `handle_params/3` loads group once ### 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 - Sorting performed at database level (`Ash.Query.sort(:name)`) for efficiency ### 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 --- #### Unit 6: Groups in Member Search **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) - **Issue 7:** Add/Remove Members in Group Detail View **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 2a: Add/Remove Members in Group Detail View **Goal:** Enable adding and removing members from groups via UI **Tasks:** 1. Add "Add Member" button above member table in Group Detail View 2. Implement modal with member search/autocomplete 3. Add "Remove" button for each member in table 4. Implement add/remove functionality with flash messages 5. Ensure proper authorization checks **Deliverables:** - Members can be added to groups via UI - Members can be removed from groups via UI - Proper feedback via flash messages - Authorization enforced **Estimation:** 2-3h **Note:** This phase extends Phase 2 and can be implemented as Issue 7 after Issue 2 is complete. ### 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: 15-21h **Note:** This includes all 7 issues. The original MVP estimation was 13-15h, with Issue 7 adding 2-3h for the add/remove members functionality in the Group Detail View. --- ## 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) ### Issue 7: Add/Remove Members in Group Detail View **Type:** Frontend **Estimation:** 2-3h **Dependencies:** Issue 1 (Backend must be functional), Issue 2 (Group Detail View must exist) **Tasks:** - Add "Add Member" button above member table in Group Detail View (`/groups/:slug`) - Implement modal for member selection with search/autocomplete - Add "Remove" button for each member in the member table - Implement add member functionality (create MemberGroup association) - Implement remove member functionality (destroy MemberGroup association) - Add flash messages for success/error feedback - Ensure proper authorization checks (only users with `:update` permission on Group can add/remove) - Filter out members already in the group from search results - Reload group data after add/remove operations **Acceptance Criteria:** - "Add Member" button is visible above member table (only for users with `:update` permission) - Clicking "Add Member" opens a modal with member search/autocomplete - Search filters members and excludes those already in the group - Selecting a member from search adds them to the group - Success flash message is displayed when member is added - Error flash message is displayed if member is already in group or other error occurs - Each member row in the table has a "Remove" button (only visible for users with `:update` permission) - Clicking "Remove" immediately removes the member from the group (no confirmation dialog) - Success flash message is displayed when member is removed - Group member list and member count update automatically after add/remove - Modal closes after successful member addition - Authorization is enforced server-side in event handlers - UI respects permission checks (buttons hidden for unauthorized users) **Technical Notes:** - Reuse member search pattern from `UserLive.Form` (ComboBox hook with autocomplete) - Use `Membership.create_member_group/1` for adding members - Use `Membership.destroy_member_group/1` for removing members - Filter search results to exclude members already in the group (check `group.members`) - Reload group with `:members` and `:member_count` after operations - Follow existing modal patterns (similar to delete confirmation modal) - Ensure accessibility: proper ARIA labels, keyboard navigation, focus management **UI/UX Details:** - Modal title: "Add Member to Group" - Search input placeholder: "Search for a member..." - Search results show member name and email - "Add" button in modal (disabled until member selected) - "Cancel" button to close modal - Remove button can be an icon button (trash icon) with tooltip - Flash messages: "Member added successfully" / "Member removed successfully" / error messages --- ## 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 uses the shared `Mv.Membership.Changes.GenerateSlug` change, which is used by both CustomFields and Groups for consistent slug generation. 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 - Used for routing (e.g., `/groups/:slug` for group detail pages) 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.