docs: update the docs
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Moritz 2025-11-13 16:56:41 +01:00
parent 10e5270273
commit 47f18e9ef3
Signed by: moritz
GPG key ID: 1020A035E5DD0824
4 changed files with 264 additions and 35 deletions

View file

@ -132,11 +132,17 @@ Member (1) → (N) Properties
### Performance Indexes ### Performance Indexes
**members:** **members:**
- `search_vector` (GIN) - Full-text search - `search_vector` (GIN) - Full-text search (tsvector)
- `email` - Email lookups - `first_name` (GIN trgm) - Fuzzy search on first name
- `last_name` - Name sorting - `last_name` (GIN trgm) - Fuzzy search on last name
- `join_date` - Date filtering - `email` (GIN trgm) - Fuzzy search on email
- `paid` (partial) - Payment status queries - `city` (GIN trgm) - Fuzzy search on city
- `street` (GIN trgm) - Fuzzy search on street
- `notes` (GIN trgm) - Fuzzy search on notes
- `email` (B-tree) - Exact email lookups
- `last_name` (B-tree) - Name sorting
- `join_date` (B-tree) - Date filtering
- `paid` (partial B-tree) - Payment status queries
**properties:** **properties:**
- `member_id` - Member property lookups - `member_id` - Member property lookups
@ -172,6 +178,64 @@ SELECT * FROM members
WHERE search_vector @@ to_tsquery('simple', 'john & doe'); WHERE search_vector @@ to_tsquery('simple', 'john & doe');
``` ```
## Fuzzy Search (Trigram-based)
### Implementation
- **Extension:** `pg_trgm` (PostgreSQL Trigram)
- **Index Type:** GIN with `gin_trgm_ops` operator class
- **Similarity Threshold:** 0.2 (default, configurable)
- **Added:** November 2025 (PR #187, closes #162)
### How It Works
Fuzzy search combines multiple search strategies:
1. **Full-text search** - Primary filter using tsvector
2. **Trigram similarity** - `similarity(field, query) > threshold`
3. **Word similarity** - `word_similarity(query, field) > threshold`
4. **Substring matching** - `LIKE` and `ILIKE` for exact substrings
5. **Modulo operator** - `query % field` for quick similarity check
### Indexed Fields for Fuzzy Search
- `first_name` - GIN trigram index
- `last_name` - GIN trigram index
- `email` - GIN trigram index
- `city` - GIN trigram index
- `street` - GIN trigram index
- `notes` - GIN trigram index
### Usage Example (Ash Action)
```elixir
# In LiveView or context
Member.fuzzy_search(Member, query: "john", similarity_threshold: 0.2)
# Or using Ash Query directly
Member
|> Ash.Query.for_read(:search, %{query: "john", similarity_threshold: 0.2})
|> Mv.Membership.read!()
```
### Usage Example (SQL)
```sql
-- Trigram similarity search
SELECT * FROM members
WHERE similarity(first_name, 'john') > 0.2
OR similarity(last_name, 'doe') > 0.2
ORDER BY similarity(first_name, 'john') DESC;
-- Word similarity (better for partial matches)
SELECT * FROM members
WHERE word_similarity('john', first_name) > 0.2;
-- Quick similarity check with % operator
SELECT * FROM members
WHERE 'john' % first_name;
```
### Performance Considerations
- **GIN indexes** speed up trigram operations significantly
- **Similarity threshold** of 0.2 balances precision and recall
- **Combined approach** (FTS + trigram) provides best results
- Lower threshold = more results but less specific
## Database Extensions ## Database Extensions
### Required PostgreSQL Extensions ### Required PostgreSQL Extensions
@ -184,10 +248,17 @@ WHERE search_vector @@ to_tsquery('simple', 'john & doe');
- Purpose: Case-insensitive text type - Purpose: Case-insensitive text type
- Used for: `users.email` (case-insensitive email matching) - Used for: `users.email` (case-insensitive email matching)
3. **pg_trgm**
- Purpose: Trigram-based fuzzy text search and similarity matching
- Used for: Fuzzy member search with similarity scoring
- Operators: `%` (similarity), `word_similarity()`, `similarity()`
- Added in: Migration `20251001141005_add_trigram_to_members.exs`
### Installation ### Installation
```sql ```sql
CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "citext"; CREATE EXTENSION IF NOT EXISTS "citext";
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
``` ```
## Migration Strategy ## Migration Strategy
@ -215,6 +286,7 @@ priv/repo/migrations/
├── 20250620110850_add_accounts_domain.exs ├── 20250620110850_add_accounts_domain.exs
├── 20250912085235_AddSearchVectorToMembers.exs ├── 20250912085235_AddSearchVectorToMembers.exs
├── 20250926180341_add_unique_email_to_members.exs ├── 20250926180341_add_unique_email_to_members.exs
├── 20251001141005_add_trigram_to_members.exs
└── 20251016130855_add_constraints_for_user_member_and_property.exs └── 20251016130855_add_constraints_for_user_member_and_property.exs
``` ```
@ -386,7 +458,7 @@ mix run priv/repo/seeds.exs
--- ---
**Last Updated:** 2025-11-10 **Last Updated:** 2025-11-13
**Schema Version:** 1.0 **Schema Version:** 1.1
**Database:** PostgreSQL 17.6 (dev) / 16 (prod) **Database:** PostgreSQL 17.6 (dev) / 16 (prod)

View file

@ -6,8 +6,8 @@
// - https://dbdocs.io // - https://dbdocs.io
// - VS Code Extensions: "DBML Language" or "dbdiagram.io" // - VS Code Extensions: "DBML Language" or "dbdiagram.io"
// //
// Version: 1.0 // Version: 1.1
// Last Updated: 2025-11-10 // Last Updated: 2025-11-13
Project mila_membership_management { Project mila_membership_management {
database_type: 'PostgreSQL' database_type: 'PostgreSQL'
@ -17,15 +17,21 @@ Project mila_membership_management {
A membership management application for small to mid-sized clubs. A membership management application for small to mid-sized clubs.
## Key Features: ## Key Features:
- User authentication (OIDC + Password) - User authentication (OIDC + Password with secure account linking)
- Member management with flexible custom properties - Member management with flexible custom properties
- Bidirectional email synchronization between users and members - Bidirectional email synchronization between users and members
- Full-text search capabilities - Full-text search capabilities (tsvector)
- Fuzzy search with trigram matching (pg_trgm)
- GDPR-compliant data management - GDPR-compliant data management
## Domains: ## Domains:
- **Accounts**: User authentication and session management - **Accounts**: User authentication and session management
- **Membership**: Club member data and custom properties - **Membership**: Club member data and custom properties
## Required PostgreSQL Extensions:
- uuid-ossp (UUID generation)
- citext (case-insensitive text)
- pg_trgm (trigram-based fuzzy search)
''' '''
} }
@ -130,10 +136,16 @@ Table members {
indexes { indexes {
email [unique, name: 'members_unique_email_index'] email [unique, name: 'members_unique_email_index']
search_vector [type: gin, name: 'members_search_vector_idx', note: 'GIN index for full-text search'] search_vector [type: gin, name: 'members_search_vector_idx', note: 'GIN index for full-text search (tsvector)']
email [name: 'members_email_idx'] first_name [type: gin, name: 'members_first_name_trgm_idx', note: 'GIN trigram index for fuzzy search']
last_name [name: 'members_last_name_idx', note: 'For name sorting'] last_name [type: gin, name: 'members_last_name_trgm_idx', note: 'GIN trigram index for fuzzy search']
join_date [name: 'members_join_date_idx', note: 'For date filters'] email [type: gin, name: 'members_email_trgm_idx', note: 'GIN trigram index for fuzzy search']
city [type: gin, name: 'members_city_trgm_idx', note: 'GIN trigram index for fuzzy search']
street [type: gin, name: 'members_street_trgm_idx', note: 'GIN trigram index for fuzzy search']
notes [type: gin, name: 'members_notes_trgm_idx', note: 'GIN trigram index for fuzzy search']
email [name: 'members_email_idx', note: 'B-tree index for exact lookups']
last_name [name: 'members_last_name_idx', note: 'B-tree index for name sorting']
join_date [name: 'members_join_date_idx', note: 'B-tree index for date filters']
(paid) [name: 'members_paid_idx', type: btree, note: 'Partial index WHERE paid IS NOT NULL'] (paid) [name: 'members_paid_idx', type: btree, note: 'Partial index WHERE paid IS NOT NULL']
} }
@ -152,10 +164,17 @@ Table members {
- Subsequent changes to either email sync bidirectionally - Subsequent changes to either email sync bidirectionally
- Validates that email is not already used by another unlinked user - Validates that email is not already used by another unlinked user
**Full-Text Search:** **Search Capabilities:**
1. Full-Text Search (tsvector):
- `search_vector` is auto-updated via trigger - `search_vector` is auto-updated via trigger
- Weighted fields: first_name (A), last_name (A), email (B), notes (B) - Weighted fields: first_name (A), last_name (A), email (B), notes (B)
- Supports flexible member search across multiple fields - GIN index for fast text search
2. Fuzzy Search (pg_trgm):
- Trigram-based similarity matching
- 6 GIN trigram indexes on searchable fields
- Configurable similarity threshold (default 0.2)
- Supports typos and partial matches
**Relationships:** **Relationships:**
- Optional 1:1 with users (0..1 ↔ 0..1) - authentication account - Optional 1:1 with users (0..1 ↔ 0..1) - authentication account

View file

@ -227,6 +227,108 @@ attribute :search_vector, AshPostgres.Tsvector,
--- ---
#### Phase 6: Search Enhancement & OIDC Improvements (Sprint 9)
**Sprint 9 - 01.11 - 13.11 (finalized)**
**PR #187:** *Implement fuzzy search* (closes #162) 🔍
- PostgreSQL `pg_trgm` extension for trigram-based fuzzy search
- 6 new GIN trigram indexes on members table:
- first_name, last_name, email, city, street, notes
- Combined search strategy: Full-text (tsvector) + Trigram similarity
- Configurable similarity threshold (default 0.2)
- Migration: `20251001141005_add_trigram_to_members.exs`
- 443 lines of comprehensive tests
**Key learnings:**
- Trigram indexes significantly improve fuzzy matching
- Combined FTS + trigram provides best user experience
- word_similarity() better for partial word matching than similarity()
- Similarity threshold of 0.2 balances precision and recall
**Implementation highlights:**
```elixir
# New Ash action: :search with fuzzy matching
read :search do
argument :query, :string, allow_nil?: true
argument :similarity_threshold, :float, allow_nil?: true
# Uses fragment() for pg_trgm operators: %, similarity(), word_similarity()
end
# Public function for LiveView usage
def fuzzy_search(query, opts) do
Ash.Query.for_read(query, :search, %{query: query_string})
end
```
---
**PR #192:** *OIDC handling and linking* (closes #171) 🔐
- Secure OIDC account linking with password verification
- Security fix: Filter OIDC sign-in by `oidc_id` instead of email
- New custom error: `PasswordVerificationRequired`
- New validation: `OidcEmailCollision` for email conflict detection
- New LiveView: `LinkOidcAccountLive` for interactive linking
- Automatic linking for passwordless users (no password prompt)
- Password verification required for password-protected accounts
- Comprehensive security logging for audit trail
- Locale persistence via secure cookie (1 year TTL)
- Documentation: `docs/oidc-account-linking.md`
**Security improvements:**
- Prevents account takeover via OIDC email matching
- Password verification before linking OIDC to password accounts
- All linking attempts logged with appropriate severity
- CSRF protection on linking forms
- Secure cookie flags: `http_only`, `secure`, `same_site: "Lax"`
**Test coverage:**
- 5 new comprehensive test files (1,793 lines total):
- `user_authentication_test.exs` (265 lines)
- `oidc_e2e_flow_test.exs` (415 lines)
- `oidc_email_update_test.exs` (271 lines)
- `oidc_password_linking_test.exs` (496 lines)
- `oidc_passwordless_linking_test.exs` (210 lines)
- Extended `oidc_integration_test.exs` (+136 lines)
**Key learnings:**
- Account linking requires careful security considerations
- Passwordless users should be auto-linked (better UX)
- Audit logging essential for security-critical operations
- Locale persistence improves user experience post-logout
---
**PR #193:** *Docs, Code Guidelines and Progress Log* 📚
- Complete project documentation suite (5,554 lines)
- New documentation files:
- `CODE_GUIDELINES.md` (2,578 lines) - Comprehensive development guidelines
- `docs/database-schema-readme.md` (392 lines) - Database documentation
- `docs/database_schema.dbml` (329 lines) - DBML schema definition
- `docs/development-progress-log.md` (1,227 lines) - This file
- `docs/feature-roadmap.md` (743 lines) - Feature planning and roadmap
- Reduced redundancy in README.md (links to detailed docs)
- Cross-referenced documentation for easy navigation
---
**PR #201:** *Code documentation and refactoring* 🔧
- @moduledoc for ALL modules (51 modules documented)
- @doc for all public functions
- Enabled Credo `ModuleDoc` check (enforces documentation standards)
- Refactored complex functions:
- `MemberLive.Index.handle_event/3` - Split sorting logic into smaller functions
- `AuthController.handle_auth_failure/2` - Reduced cyclomatic complexity
- Documentation coverage: 100% for core modules
**Key learnings:**
- @moduledoc enforcement improves code maintainability
- Refactoring complex functions improves readability
- Documentation should explain "why" not just "what"
- Credo helps maintain consistent code quality
---
## Implementation Decisions ## Implementation Decisions
### Architecture Patterns ### Architecture Patterns
@ -369,9 +471,11 @@ end
- ✅ Consistent styling - ✅ Consistent styling
- ✅ Mobile-responsive out of the box - ✅ Mobile-responsive out of the box
#### 7. Full-Text Search Implementation #### 7. Search Implementation (Full-Text + Fuzzy)
**PostgreSQL tsvector + GIN Index** **Two-Tiered Search Strategy:**
**A) Full-Text Search (tsvector + GIN Index)**
```sql ```sql
-- Auto-updating trigger -- Auto-updating trigger
@ -389,16 +493,40 @@ END
$$ LANGUAGE plpgsql; $$ LANGUAGE plpgsql;
``` ```
**B) Fuzzy Search (pg_trgm + Trigram GIN Indexes)**
Added November 2025 (PR #187):
```elixir
# Ash action combining FTS + trigram similarity
read :search do
argument :query, :string
argument :similarity_threshold, :float
prepare fn query, _ctx ->
# 1. Full-text search (tsvector)
# 2. Trigram similarity (%, similarity(), word_similarity())
# 3. Substring matching (contains, ilike)
end
end
```
**6 Trigram Indexes:**
- first_name, last_name, email, city, street, notes
- GIN index with `gin_trgm_ops` operator class
**Reasoning:** **Reasoning:**
- Native PostgreSQL feature (no external service) - Native PostgreSQL features (no external service)
- Fast with GIN index - Combined approach handles typos + partial matches
- Weighted fields (names more important than dates) - Fast with GIN indexes
- Simple lexer (no German stemming initially) - Simple lexer (no German stemming initially)
- Similarity threshold configurable (default 0.2)
**Why not Elasticsearch/Meilisearch?** **Why not Elasticsearch/Meilisearch?**
- Overkill for small to mid-sized clubs - Overkill for small to mid-sized clubs
- Additional infrastructure complexity - Additional infrastructure complexity
- PostgreSQL full-text sufficient for 10k+ members - PostgreSQL full-text + fuzzy sufficient for 10k+ members
- Better integration with existing stack
### Deviations from Initial Plans ### Deviations from Initial Plans
@ -470,7 +598,8 @@ end
3. `20250620110850_add_accounts_domain.exs` - Users & tokens tables 3. `20250620110850_add_accounts_domain.exs` - Users & tokens tables
4. `20250912085235_AddSearchVectorToMembers.exs` - Full-text search (tsvector + GIN index) 4. `20250912085235_AddSearchVectorToMembers.exs` - Full-text search (tsvector + GIN index)
5. `20250926164519_member_relation.exs` - User-Member link (optional 1:1) 5. `20250926164519_member_relation.exs` - User-Member link (optional 1:1)
6. `20251016130855_add_constraints_for_user_member_and_property.exs` - Email sync constraints 6. `20251001141005_add_trigram_to_members.exs` - Fuzzy search (pg_trgm + 6 GIN trigram indexes)
7. `20251016130855_add_constraints_for_user_member_and_property.exs` - Email sync constraints
**Learning:** Ash's code generation from resources ensures schema always matches code. **Learning:** Ash's code generation from resources ensures schema always matches code.
@ -1220,8 +1349,8 @@ This project demonstrates a modern Phoenix application built with:
--- ---
**Document Version:** 1.0 **Document Version:** 1.1
**Last Updated:** 2025-11-10 **Last Updated:** 2025-11-13
**Maintainer:** Development Team **Maintainer:** Development Team
**Status:** Living Document (update as project evolves) **Status:** Living Document (update as project evolves)

View file

@ -26,9 +26,14 @@
- ✅ Password-based authentication - ✅ Password-based authentication
- ✅ User sessions and tokens - ✅ User sessions and tokens
- ✅ Basic authentication flows - ✅ Basic authentication flows
- ✅ **OIDC account linking with password verification** (PR #192, closes #171)
- ✅ **Secure OIDC email collision handling** (PR #192)
- ✅ **Automatic linking for passwordless users** (PR #192)
**Closed Issues:**
- ✅ [#171](https://git.local-it.org/local-it/mitgliederverwaltung/issues/171) - OIDC handling and linking (closed 2025-11-13)
**Open Issues:** **Open Issues:**
- [#171](https://git.local-it.org/local-it/mitgliederverwaltung/issues/171) - Ensure correct handling of Password login vs OIDC login (M)
- [#146](https://git.local-it.org/local-it/mitgliederverwaltung/issues/146) - Translate "or" in the login screen (Low) - [#146](https://git.local-it.org/local-it/mitgliederverwaltung/issues/146) - Translate "or" in the login screen (Low)
- [#144](https://git.local-it.org/local-it/mitgliederverwaltung/issues/144) - Add language switch dropdown to login screen (Low) - [#144](https://git.local-it.org/local-it/mitgliederverwaltung/issues/144) - Add language switch dropdown to login screen (Low)
@ -54,20 +59,24 @@
- ✅ Address management - ✅ Address management
- ✅ Membership status tracking - ✅ Membership status tracking
- ✅ Full-text search (PostgreSQL tsvector) - ✅ Full-text search (PostgreSQL tsvector)
- ✅ **Fuzzy search with trigram matching** (PR #187, closes #162)
- ✅ **Combined FTS + trigram search** (PR #187)
- ✅ **6 GIN trigram indexes** for fuzzy matching (PR #187)
- ✅ Sorting by basic fields - ✅ Sorting by basic fields
- ✅ User-Member linking (optional 1:1) - ✅ User-Member linking (optional 1:1)
- ✅ Email synchronization between User and Member - ✅ Email synchronization between User and Member
**Closed Issues:**
- ✅ [#162](https://git.local-it.org/local-it/mitgliederverwaltung/issues/162) - Fuzzy and substring search (closed 2025-11-12)
**Open Issues:** **Open Issues:**
- [#169](https://git.local-it.org/local-it/mitgliederverwaltung/issues/169) - Allow combined creation of Users/Members (M, Low priority) - [#169](https://git.local-it.org/local-it/mitgliederverwaltung/issues/169) - Allow combined creation of Users/Members (M, Low priority)
- [#168](https://git.local-it.org/local-it/mitgliederverwaltung/issues/168) - Allow user-member association in edit/create views (M, High priority) - [#168](https://git.local-it.org/local-it/mitgliederverwaltung/issues/168) - Allow user-member association in edit/create views (M, High priority)
- [#165](https://git.local-it.org/local-it/mitgliederverwaltung/issues/165) - Pagination for list of members (S, Low priority) - [#165](https://git.local-it.org/local-it/mitgliederverwaltung/issues/165) - Pagination for list of members (S, Low priority)
- [#162](https://git.local-it.org/local-it/mitgliederverwaltung/issues/162) - Implement fuzzy and substring search (M, Medium priority)
- [#160](https://git.local-it.org/local-it/mitgliederverwaltung/issues/160) - Implement clear icon in searchbar (S, Low priority) - [#160](https://git.local-it.org/local-it/mitgliederverwaltung/issues/160) - Implement clear icon in searchbar (S, Low priority)
- [#154](https://git.local-it.org/local-it/mitgliederverwaltung/issues/154) - Concept advanced search (Low priority, needs refinement) - [#154](https://git.local-it.org/local-it/mitgliederverwaltung/issues/154) - Concept advanced search (Low priority, needs refinement)
**Missing Features:** **Missing Features:**
- ❌ Fuzzy search
- ❌ Advanced filters (date ranges, multiple criteria) - ❌ Advanced filters (date ranges, multiple criteria)
- ❌ Pagination (currently all members loaded) - ❌ Pagination (currently all members loaded)
- ❌ Bulk operations (bulk delete, bulk update) - ❌ Bulk operations (bulk delete, bulk update)
@ -367,8 +376,8 @@
| Feature Area | Current Status | Priority | Complexity | | Feature Area | Current Status | Priority | Complexity |
|--------------|----------------|----------|------------| |--------------|----------------|----------|------------|
| **Authentication & Authorization** | 40% complete | **High** | Medium | | **Authentication & Authorization** | 60% complete | **High** | Medium |
| **Member Management** | 70% complete | **High** | Low-Medium | | **Member Management** | 85% complete | **High** | Low-Medium |
| **Custom Fields** | 50% complete | **High** | Medium | | **Custom Fields** | 50% complete | **High** | Medium |
| **User Management** | 60% complete | Medium | Low | | **User Management** | 60% complete | Medium | Low |
| **Navigation & UX** | 50% complete | Medium | Low | | **Navigation & UX** | 50% complete | Medium | Low |
@ -388,12 +397,12 @@
### Open Milestones (From Issues) ### Open Milestones (From Issues)
1. ✅ **Ich kann einen neuen Kontakt anlegen** (Closed) 1. ✅ **Ich kann einen neuen Kontakt anlegen** (Closed)
2. 🔄 **I can search through the list of members - fulltext** (Open) - Related: #162, #154 2. **I can search through the list of members - fulltext** (Closed) - #162 implemented (Fuzzy Search), #154 needs refinement
3. 🔄 **I can sort the list of members for specific fields** (Open) - Related: #153 3. 🔄 **I can sort the list of members for specific fields** (Open) - Related: #153
4. 🔄 **We have a intuitive navigation structure** (Open) 4. 🔄 **We have a intuitive navigation structure** (Open)
5. 🔄 **We have different roles and permissions** (Open) - Related: #191, #190, #151 5. 🔄 **We have different roles and permissions** (Open) - Related: #191, #190, #151
6. 🔄 **As Admin I can configure settings globally** (Open) 6. 🔄 **As Admin I can configure settings globally** (Open)
7. 🔄 **Accounts & Logins** (Open) - Related: #171, #169, #168 7. **Accounts & Logins** (Partially closed) - #171 implemented (OIDC linking), #169/#168 still open
8. 🔄 **I can add custom fields** (Open) - Related: #194, #157, #161 8. 🔄 **I can add custom fields** (Open) - Related: #194, #157, #161
9. 🔄 **Import transactions via vereinfacht API** (Open) - Related: #156 9. 🔄 **Import transactions via vereinfacht API** (Open) - Related: #156
10. 🔄 **We have a staging environment** (Open) 10. 🔄 **We have a staging environment** (Open)