Merge pull request 'Docs, Code Guidelines and Progress Log' (#193) from feature/guidelines_docs into main
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #193
This commit is contained in:
commit
a4ed2498e7
6 changed files with 5289 additions and 16 deletions
2576
CODE_GUIDELINES.md
Normal file
2576
CODE_GUIDELINES.md
Normal file
File diff suppressed because it is too large
Load diff
38
README.md
38
README.md
|
|
@ -161,27 +161,33 @@ Now you can log in to Mila via OIDC!
|
|||
|
||||
## 🏗️ Architecture
|
||||
|
||||
- **Backend:** Elixir, Phoenix, LiveView, Ash Framework
|
||||
- **Frontend:** Phoenix LiveView + DaisyUI + Heroicons
|
||||
- **Database:** PostgreSQL (via AshPostgres)
|
||||
- **Auth:** AshAuthentication (OIDC + password strategy)
|
||||
- **Mail:** Swoosh
|
||||
- **i18n:** Gettext
|
||||
**Tech Stack Overview:**
|
||||
- **Backend:** Elixir + Phoenix + Ash Framework
|
||||
- **Frontend:** Phoenix LiveView + Tailwind CSS + DaisyUI
|
||||
- **Database:** PostgreSQL
|
||||
- **Auth:** AshAuthentication (OIDC + password)
|
||||
|
||||
Code structure:
|
||||
- `lib/mv/` — core Ash resources/domains (`Accounts`, `Membership`)
|
||||
**Code Structure:**
|
||||
- `lib/accounts/` & `lib/membership/` — Ash resources and domains
|
||||
- `lib/mv_web/` — Phoenix controllers, LiveViews, components
|
||||
- `assets/` — frontend assets (Tailwind, JS, etc.)
|
||||
- `assets/` — Tailwind, JavaScript, static files
|
||||
|
||||
📚 **Full tech stack details:** See [`CODE_GUIDELINES.md`](CODE_GUIDELINES.md)
|
||||
📖 **Implementation history:** See [`docs/development-progress-log.md`](docs/development-progress-log.md)
|
||||
🗄️ **Database schema:** See [`docs/database-schema-readme.md`](docs/database-schema-readme.md)
|
||||
|
||||
## 🧑💻 Development
|
||||
|
||||
Useful `just` commands:
|
||||
- `just run` — start DB, Mailcrab, Rauthy, app
|
||||
- `just test` — run tests
|
||||
- `just lint` — run code style checks (credo, formatter)
|
||||
- `just audit` — run security audits
|
||||
- `just reset-database` — reset local DB
|
||||
- `just regen-migrations <name>` — regenerate migrations
|
||||
**Common commands:**
|
||||
```bash
|
||||
just run # Start full dev environment
|
||||
just test # Run test suite
|
||||
just lint # Code style checks
|
||||
just audit # Security audits
|
||||
just reset-database # Reset local DB
|
||||
```
|
||||
|
||||
📚 **Full development guidelines:** See [`CODE_GUIDELINES.md`](CODE_GUIDELINES.md)
|
||||
|
||||
## 📦 Production Deployment
|
||||
|
||||
|
|
|
|||
392
docs/database-schema-readme.md
Normal file
392
docs/database-schema-readme.md
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
# Database Schema Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
This document provides a comprehensive overview of the Mila Membership Management System database schema.
|
||||
|
||||
## Quick Links
|
||||
|
||||
- **DBML File:** [`database_schema.dbml`](./database_schema.dbml)
|
||||
- **Visualize Online:**
|
||||
- [dbdiagram.io](https://dbdiagram.io) - Upload the DBML file
|
||||
- [dbdocs.io](https://dbdocs.io) - Generate interactive documentation
|
||||
|
||||
## Schema Statistics
|
||||
|
||||
| Metric | Count |
|
||||
|--------|-------|
|
||||
| **Tables** | 5 |
|
||||
| **Domains** | 2 (Accounts, Membership) |
|
||||
| **Relationships** | 3 |
|
||||
| **Indexes** | 15+ |
|
||||
| **Triggers** | 1 (Full-text search) |
|
||||
|
||||
## Tables Overview
|
||||
|
||||
### Accounts Domain
|
||||
|
||||
#### `users`
|
||||
- **Purpose:** User authentication and session management
|
||||
- **Rows (Estimated):** Low to Medium (typically 10-50% of members)
|
||||
- **Key Features:**
|
||||
- Dual authentication (Password + OIDC)
|
||||
- Optional 1:1 link to members
|
||||
- Email as source of truth when linked
|
||||
|
||||
#### `tokens`
|
||||
- **Purpose:** JWT token storage for AshAuthentication
|
||||
- **Rows (Estimated):** Medium to High (multiple tokens per user)
|
||||
- **Key Features:**
|
||||
- Token lifecycle management
|
||||
- Revocation support
|
||||
- Multiple token purposes
|
||||
|
||||
### Membership Domain
|
||||
|
||||
#### `members`
|
||||
- **Purpose:** Club member master data
|
||||
- **Rows (Estimated):** High (core entity)
|
||||
- **Key Features:**
|
||||
- Complete member profile
|
||||
- Full-text search via tsvector
|
||||
- Bidirectional email sync with users
|
||||
- Flexible address and contact data
|
||||
|
||||
#### `properties`
|
||||
- **Purpose:** Dynamic custom member attributes
|
||||
- **Rows (Estimated):** Variable (N per member)
|
||||
- **Key Features:**
|
||||
- Union type value storage (JSONB)
|
||||
- Multiple data types supported
|
||||
- One property per type per member
|
||||
|
||||
#### `property_types`
|
||||
- **Purpose:** Schema definitions for custom properties
|
||||
- **Rows (Estimated):** Low (admin-defined)
|
||||
- **Key Features:**
|
||||
- Type definitions
|
||||
- Immutable and required flags
|
||||
- Centralized property management
|
||||
|
||||
## Key Relationships
|
||||
|
||||
```
|
||||
User (0..1) ←→ (0..1) Member
|
||||
↓
|
||||
Tokens (N)
|
||||
|
||||
Member (1) → (N) Properties
|
||||
↓
|
||||
PropertyType (1)
|
||||
```
|
||||
|
||||
### Relationship Details
|
||||
|
||||
1. **User ↔ Member (Optional 1:1, both sides optional)**
|
||||
- A User can have 0 or 1 Member (`user.member_id` can be NULL)
|
||||
- A Member can have 0 or 1 User (optional `has_one` relationship)
|
||||
- Both entities can exist independently
|
||||
- Email synchronization when linked (User.email is source of truth)
|
||||
- `ON DELETE SET NULL` on user side (User preserved when Member deleted)
|
||||
|
||||
2. **Member → Properties (1:N)**
|
||||
- One member, many properties
|
||||
- `ON DELETE CASCADE` - properties deleted with member
|
||||
- Composite unique constraint (member_id, property_type_id)
|
||||
|
||||
3. **Property → PropertyType (N:1)**
|
||||
- Properties reference type definition
|
||||
- `ON DELETE RESTRICT` - cannot delete type if in use
|
||||
- Type defines data structure
|
||||
|
||||
## Important Business Rules
|
||||
|
||||
### Email Synchronization
|
||||
- **User.email** is the source of truth when linked
|
||||
- On linking: Member.email ← User.email (overwrite)
|
||||
- After linking: Changes sync bidirectionally
|
||||
- Validation prevents email conflicts
|
||||
|
||||
### Authentication Strategies
|
||||
- **Password:** Email + hashed_password
|
||||
- **OIDC:** Email + oidc_id (Rauthy provider)
|
||||
- At least one method required per user
|
||||
|
||||
### Member Constraints
|
||||
- First name and last name required (min 1 char)
|
||||
- Email unique, validated format (5-254 chars)
|
||||
- Birth date cannot be in future
|
||||
- Join date cannot be in future
|
||||
- Exit date must be after join date
|
||||
- Phone: `+?[0-9\- ]{6,20}`
|
||||
- Postal code: 5 digits
|
||||
|
||||
### Property System
|
||||
- Maximum one property per type per member
|
||||
- Value stored as union type in JSONB
|
||||
- Supported types: string, integer, boolean, date, email
|
||||
- Types can be marked as immutable or required
|
||||
|
||||
## Indexes
|
||||
|
||||
### Performance Indexes
|
||||
|
||||
**members:**
|
||||
- `search_vector` (GIN) - Full-text search
|
||||
- `email` - Email lookups
|
||||
- `last_name` - Name sorting
|
||||
- `join_date` - Date filtering
|
||||
- `paid` (partial) - Payment status queries
|
||||
|
||||
**properties:**
|
||||
- `member_id` - Member property lookups
|
||||
- `property_type_id` - Type-based queries
|
||||
- Composite `(member_id, property_type_id)` - Uniqueness
|
||||
|
||||
**tokens:**
|
||||
- `subject` - User token lookups
|
||||
- `expires_at` - Token cleanup
|
||||
- `purpose` - Purpose-based queries
|
||||
|
||||
**users:**
|
||||
- `email` (unique) - Login lookups
|
||||
- `oidc_id` (unique) - OIDC authentication
|
||||
- `member_id` (unique) - Member linkage
|
||||
|
||||
## Full-Text Search
|
||||
|
||||
### Implementation
|
||||
- **Trigger:** `members_search_vector_trigger()`
|
||||
- **Function:** Automatically updates `search_vector` on INSERT/UPDATE
|
||||
- **Index Type:** GIN (Generalized Inverted Index)
|
||||
|
||||
### Weighted Fields
|
||||
- **Weight A (highest):** first_name, last_name
|
||||
- **Weight B:** email, notes
|
||||
- **Weight C:** birth_date, phone_number, city, street, house_number, postal_code
|
||||
- **Weight D (lowest):** join_date, exit_date
|
||||
|
||||
### Usage Example
|
||||
```sql
|
||||
SELECT * FROM members
|
||||
WHERE search_vector @@ to_tsquery('simple', 'john & doe');
|
||||
```
|
||||
|
||||
## Database Extensions
|
||||
|
||||
### Required PostgreSQL Extensions
|
||||
|
||||
1. **uuid-ossp**
|
||||
- Purpose: UUID generation functions
|
||||
- Used for: `gen_random_uuid()`, `uuid_generate_v7()`
|
||||
|
||||
2. **citext**
|
||||
- Purpose: Case-insensitive text type
|
||||
- Used for: `users.email` (case-insensitive email matching)
|
||||
|
||||
### Installation
|
||||
```sql
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
CREATE EXTENSION IF NOT EXISTS "citext";
|
||||
```
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Ash Migrations
|
||||
This project uses Ash Framework's migration system:
|
||||
|
||||
```bash
|
||||
# Generate new migration
|
||||
mix ash.codegen --name add_new_feature
|
||||
|
||||
# Apply migrations
|
||||
mix ash.setup
|
||||
|
||||
# Rollback migrations
|
||||
mix ash_postgres.rollback -n 1
|
||||
```
|
||||
|
||||
### Migration Files Location
|
||||
```
|
||||
priv/repo/migrations/
|
||||
├── 20250421101957_initialize_extensions_1.exs
|
||||
├── 20250528163901_initial_migration.exs
|
||||
├── 20250617090641_member_fields.exs
|
||||
├── 20250620110850_add_accounts_domain.exs
|
||||
├── 20250912085235_AddSearchVectorToMembers.exs
|
||||
├── 20250926180341_add_unique_email_to_members.exs
|
||||
└── 20251016130855_add_constraints_for_user_member_and_property.exs
|
||||
```
|
||||
|
||||
## Data Integrity
|
||||
|
||||
### Foreign Key Behaviors
|
||||
|
||||
| Relationship | On Delete | Rationale |
|
||||
|--------------|-----------|-----------|
|
||||
| `users.member_id → members.id` | SET NULL | Preserve user account when member deleted |
|
||||
| `properties.member_id → members.id` | CASCADE | Delete properties with member |
|
||||
| `properties.property_type_id → property_types.id` | RESTRICT | Prevent deletion of types in use |
|
||||
|
||||
### Validation Layers
|
||||
|
||||
1. **Database Level:**
|
||||
- CHECK constraints
|
||||
- NOT NULL constraints
|
||||
- UNIQUE indexes
|
||||
- Foreign key constraints
|
||||
|
||||
2. **Application Level (Ash):**
|
||||
- Custom validators
|
||||
- Email format validation (EctoCommons.EmailValidator)
|
||||
- Business rule validation
|
||||
- Cross-entity validation
|
||||
|
||||
3. **UI Level:**
|
||||
- Client-side form validation
|
||||
- Real-time feedback
|
||||
- Error messages
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Query Patterns
|
||||
|
||||
**High Frequency:**
|
||||
- Member search (uses GIN index on search_vector)
|
||||
- Member list with filters (uses indexes on join_date, paid)
|
||||
- User authentication (uses unique index on email/oidc_id)
|
||||
- Property lookups by member (uses index on member_id)
|
||||
|
||||
**Medium Frequency:**
|
||||
- Member CRUD operations
|
||||
- Property updates
|
||||
- Token validation
|
||||
|
||||
**Low Frequency:**
|
||||
- PropertyType management
|
||||
- User-Member linking
|
||||
- Bulk operations
|
||||
|
||||
### Optimization Tips
|
||||
|
||||
1. **Use indexes:** All critical query paths have indexes
|
||||
2. **Preload relationships:** Use Ash's `load` to avoid N+1
|
||||
3. **Pagination:** Use keyset pagination (configured by default)
|
||||
4. **Partial indexes:** `members.paid` index only non-NULL values
|
||||
5. **Search optimization:** Full-text search via tsvector, not LIKE
|
||||
|
||||
## Visualization
|
||||
|
||||
### Using dbdiagram.io
|
||||
|
||||
1. Visit [https://dbdiagram.io](https://dbdiagram.io)
|
||||
2. Click "Import" → "From file"
|
||||
3. Upload `database_schema.dbml`
|
||||
4. View interactive diagram with relationships
|
||||
|
||||
### Using dbdocs.io
|
||||
|
||||
1. Install dbdocs CLI: `npm install -g dbdocs`
|
||||
2. Generate docs: `dbdocs build database_schema.dbml`
|
||||
3. View generated documentation
|
||||
|
||||
### VS Code Extension
|
||||
|
||||
Install "DBML Language" extension to view/edit DBML files with:
|
||||
- Syntax highlighting
|
||||
- Inline documentation
|
||||
- Error checking
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Sensitive Data
|
||||
|
||||
**Encrypted:**
|
||||
- `users.hashed_password` (bcrypt)
|
||||
|
||||
**Should Not Log:**
|
||||
- hashed_password
|
||||
- tokens (jti, purpose, extra_data)
|
||||
|
||||
**Personal Data (GDPR):**
|
||||
- All member fields (name, email, birth_date, address)
|
||||
- User email
|
||||
- Token subject
|
||||
|
||||
### Access Control
|
||||
|
||||
- Implement through Ash policies
|
||||
- Row-level security considerations for future
|
||||
- Audit logging for sensitive operations
|
||||
|
||||
## Backup Recommendations
|
||||
|
||||
### Critical Tables (Priority 1)
|
||||
- `members` - Core business data
|
||||
- `users` - Authentication data
|
||||
- `property_types` - Schema definitions
|
||||
|
||||
### Important Tables (Priority 2)
|
||||
- `properties` - Member custom data
|
||||
- `tokens` - Can be regenerated but good to backup
|
||||
|
||||
### Backup Strategy
|
||||
```bash
|
||||
# Full database backup
|
||||
pg_dump -Fc mv_prod > backup_$(date +%Y%m%d).dump
|
||||
|
||||
# Restore
|
||||
pg_restore -d mv_prod backup_20251110.dump
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Test Database
|
||||
- Separate test database: `mv_test`
|
||||
- Sandbox mode via Ecto.Adapters.SQL.Sandbox
|
||||
- Reset between tests
|
||||
|
||||
### Seed Data
|
||||
```bash
|
||||
# Load seed data
|
||||
mix run priv/repo/seeds.exs
|
||||
```
|
||||
|
||||
## Future Considerations
|
||||
|
||||
### Potential Additions
|
||||
|
||||
1. **Audit Log Table**
|
||||
- Track changes to members
|
||||
- Compliance and history tracking
|
||||
|
||||
2. **Payment Tracking**
|
||||
- Payment history table
|
||||
- Transaction records
|
||||
- Fee calculation
|
||||
|
||||
3. **Document Storage**
|
||||
- Member documents/attachments
|
||||
- File metadata table
|
||||
|
||||
4. **Email Queue**
|
||||
- Outbound email tracking
|
||||
- Delivery status
|
||||
|
||||
5. **Roles & Permissions**
|
||||
- User roles (admin, treasurer, member)
|
||||
- Permission management
|
||||
|
||||
## Resources
|
||||
|
||||
- **Ash Framework:** [https://hexdocs.pm/ash](https://hexdocs.pm/ash)
|
||||
- **AshPostgres:** [https://hexdocs.pm/ash_postgres](https://hexdocs.pm/ash_postgres)
|
||||
- **DBML Specification:** [https://dbml.dbdiagram.io](https://dbml.dbdiagram.io)
|
||||
- **PostgreSQL Docs:** [https://www.postgresql.org/docs/](https://www.postgresql.org/docs/)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** 2025-11-10
|
||||
**Schema Version:** 1.0
|
||||
**Database:** PostgreSQL 17.6 (dev) / 16 (prod)
|
||||
|
||||
329
docs/database_schema.dbml
Normal file
329
docs/database_schema.dbml
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
// Mila - Membership Management System
|
||||
// Database Schema Documentation
|
||||
//
|
||||
// This file can be used with:
|
||||
// - https://dbdiagram.io
|
||||
// - https://dbdocs.io
|
||||
// - VS Code Extensions: "DBML Language" or "dbdiagram.io"
|
||||
//
|
||||
// Version: 1.0
|
||||
// Last Updated: 2025-11-10
|
||||
|
||||
Project mila_membership_management {
|
||||
database_type: 'PostgreSQL'
|
||||
Note: '''
|
||||
# Mila Membership Management System
|
||||
|
||||
A membership management application for small to mid-sized clubs.
|
||||
|
||||
## Key Features:
|
||||
- User authentication (OIDC + Password)
|
||||
- Member management with flexible custom properties
|
||||
- Bidirectional email synchronization between users and members
|
||||
- Full-text search capabilities
|
||||
- GDPR-compliant data management
|
||||
|
||||
## Domains:
|
||||
- **Accounts**: User authentication and session management
|
||||
- **Membership**: Club member data and custom properties
|
||||
'''
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// ACCOUNTS DOMAIN
|
||||
// ============================================
|
||||
|
||||
Table users {
|
||||
id uuid [pk, not null, default: `gen_random_uuid()`, note: 'Primary identifier']
|
||||
email citext [not null, unique, note: 'Email address (case-insensitive) - source of truth when linked to member']
|
||||
hashed_password text [null, note: 'Bcrypt-hashed password (null for OIDC-only users)']
|
||||
oidc_id text [null, unique, note: 'External OIDC identifier from authentication provider (e.g., Rauthy)']
|
||||
member_id uuid [null, unique, note: 'Optional 1:1 link to member record']
|
||||
|
||||
indexes {
|
||||
email [unique, name: 'users_unique_email_index']
|
||||
oidc_id [unique, name: 'users_unique_oidc_id_index']
|
||||
member_id [unique, name: 'users_unique_member_index']
|
||||
}
|
||||
|
||||
Note: '''
|
||||
**User Authentication Table**
|
||||
|
||||
Handles user login accounts with two authentication strategies:
|
||||
1. Password-based authentication (email + hashed_password)
|
||||
2. OIDC/SSO authentication (email + oidc_id)
|
||||
|
||||
**Relationship with Members:**
|
||||
- Optional 1:1 relationship with members table (0..1 ↔ 0..1)
|
||||
- A user can have 0 or 1 member (user.member_id can be NULL)
|
||||
- A member can have 0 or 1 user (optional has_one relationship)
|
||||
- Both entities can exist independently
|
||||
- When linked, user.email is the source of truth
|
||||
- Email changes sync bidirectionally between user ↔ member
|
||||
|
||||
**Constraints:**
|
||||
- At least one auth method required (password OR oidc_id)
|
||||
- Email must be unique across all users
|
||||
- OIDC ID must be unique if present
|
||||
- Member can only be linked to one user (enforced by unique index)
|
||||
|
||||
**Deletion Behavior:**
|
||||
- When member is deleted → user.member_id set to NULL (user preserved)
|
||||
- When user is deleted → member.user relationship cleared (member preserved)
|
||||
'''
|
||||
}
|
||||
|
||||
Table tokens {
|
||||
jti text [pk, not null, note: 'JWT ID - unique token identifier']
|
||||
subject text [not null, note: 'Token subject (usually user ID)']
|
||||
purpose text [not null, note: 'Token purpose (e.g., "access", "refresh", "password_reset")']
|
||||
expires_at timestamp [not null, note: 'Token expiration timestamp (UTC)']
|
||||
extra_data jsonb [null, note: 'Additional token metadata']
|
||||
created_at timestamp [not null, default: `now() AT TIME ZONE 'utc'`, note: 'Creation timestamp (UTC)']
|
||||
updated_at timestamp [not null, default: `now() AT TIME ZONE 'utc'`, note: 'Last update timestamp (UTC)']
|
||||
|
||||
indexes {
|
||||
subject [name: 'tokens_subject_idx', note: 'For user token lookups']
|
||||
expires_at [name: 'tokens_expires_at_idx', note: 'For token cleanup queries']
|
||||
purpose [name: 'tokens_purpose_idx', note: 'For purpose-based queries']
|
||||
}
|
||||
|
||||
Note: '''
|
||||
**AshAuthentication Token Management**
|
||||
|
||||
Stores JWT tokens for authentication and authorization.
|
||||
|
||||
**Token Purposes:**
|
||||
- `access`: Short-lived access tokens for API requests
|
||||
- `refresh`: Long-lived tokens for obtaining new access tokens
|
||||
- `password_reset`: Temporary tokens for password reset flow
|
||||
- `email_confirmation`: Temporary tokens for email verification
|
||||
|
||||
**Token Lifecycle:**
|
||||
- Tokens are created during login/registration
|
||||
- Can be revoked by deleting the record
|
||||
- Expired tokens should be cleaned up periodically
|
||||
- `store_all_tokens? true` enables token tracking
|
||||
'''
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// MEMBERSHIP DOMAIN
|
||||
// ============================================
|
||||
|
||||
Table members {
|
||||
id uuid [pk, not null, default: `uuid_generate_v7()`, note: 'UUIDv7 primary key (sortable by creation time)']
|
||||
first_name text [not null, note: 'Member first name (min length: 1)']
|
||||
last_name text [not null, note: 'Member last name (min length: 1)']
|
||||
email text [not null, unique, note: 'Member email address (5-254 chars, validated)']
|
||||
birth_date date [null, note: 'Date of birth (cannot be in future)']
|
||||
paid boolean [null, note: 'Payment status flag']
|
||||
phone_number text [null, note: 'Contact phone number (format: +?[0-9\- ]{6,20})']
|
||||
join_date date [null, note: 'Date when member joined club (cannot be in future)']
|
||||
exit_date date [null, note: 'Date when member left club (must be after join_date)']
|
||||
notes text [null, note: 'Additional notes about member']
|
||||
city text [null, note: 'City of residence']
|
||||
street text [null, note: 'Street name']
|
||||
house_number text [null, note: 'House number']
|
||||
postal_code text [null, note: '5-digit German postal code']
|
||||
search_vector tsvector [null, note: 'Full-text search index (auto-generated)']
|
||||
|
||||
indexes {
|
||||
email [unique, name: 'members_unique_email_index']
|
||||
search_vector [type: gin, name: 'members_search_vector_idx', note: 'GIN index for full-text search']
|
||||
email [name: 'members_email_idx']
|
||||
last_name [name: 'members_last_name_idx', note: 'For name sorting']
|
||||
join_date [name: 'members_join_date_idx', note: 'For date filters']
|
||||
(paid) [name: 'members_paid_idx', type: btree, note: 'Partial index WHERE paid IS NOT NULL']
|
||||
}
|
||||
|
||||
Note: '''
|
||||
**Club Member Master Data**
|
||||
|
||||
Core entity for membership management containing:
|
||||
- Personal information (name, birth date, email)
|
||||
- Contact details (phone, address)
|
||||
- Membership status (join/exit dates, payment status)
|
||||
- Additional notes
|
||||
|
||||
**Email Synchronization:**
|
||||
When a member is linked to a user:
|
||||
- User.email is the source of truth (overwrites member.email on link)
|
||||
- Subsequent changes to either email sync bidirectionally
|
||||
- Validates that email is not already used by another unlinked user
|
||||
|
||||
**Full-Text Search:**
|
||||
- `search_vector` is auto-updated via trigger
|
||||
- Weighted fields: first_name (A), last_name (A), email (B), notes (B)
|
||||
- Supports flexible member search across multiple fields
|
||||
|
||||
**Relationships:**
|
||||
- Optional 1:1 with users (0..1 ↔ 0..1) - authentication account
|
||||
- 1:N with properties (custom dynamic fields)
|
||||
|
||||
**Validation Rules:**
|
||||
- first_name, last_name: min 1 character
|
||||
- email: 5-254 characters, valid email format
|
||||
- birth_date: cannot be in future
|
||||
- join_date: cannot be in future
|
||||
- exit_date: must be after join_date (if both present)
|
||||
- phone_number: matches pattern ^\+?[0-9\- ]{6,20}$
|
||||
- postal_code: exactly 5 digits
|
||||
'''
|
||||
}
|
||||
|
||||
Table properties {
|
||||
id uuid [pk, not null, default: `gen_random_uuid()`, note: 'Primary identifier']
|
||||
value jsonb [null, note: 'Union type value storage (format: {type: "string", value: "example"})']
|
||||
member_id uuid [not null, note: 'Link to member']
|
||||
property_type_id uuid [not null, note: 'Link to property type definition']
|
||||
|
||||
indexes {
|
||||
(member_id, property_type_id) [unique, name: 'properties_unique_property_per_member_index', note: 'One property per type per member']
|
||||
member_id [name: 'properties_member_id_idx']
|
||||
property_type_id [name: 'properties_property_type_id_idx']
|
||||
}
|
||||
|
||||
Note: '''
|
||||
**Dynamic Custom Member Properties**
|
||||
|
||||
Provides flexible, extensible attributes for members beyond the fixed schema.
|
||||
|
||||
**Value Storage:**
|
||||
- Stored as JSONB map with type discrimination
|
||||
- Format: `{type: "string|integer|boolean|date|email", value: <actual_value>}`
|
||||
- Allows multiple data types in single column
|
||||
|
||||
**Supported Types:**
|
||||
- `string`: Text data
|
||||
- `integer`: Numeric data
|
||||
- `boolean`: True/False flags
|
||||
- `date`: Date values
|
||||
- `email`: Validated email addresses
|
||||
|
||||
**Constraints:**
|
||||
- Each member can have only ONE property per property_type
|
||||
- Properties are deleted when member is deleted (CASCADE)
|
||||
- Property type cannot be deleted if properties exist (RESTRICT)
|
||||
|
||||
**Use Cases:**
|
||||
- Custom membership numbers
|
||||
- Additional contact methods
|
||||
- Club-specific attributes
|
||||
- Flexible data model without schema migrations
|
||||
'''
|
||||
}
|
||||
|
||||
Table property_types {
|
||||
id uuid [pk, not null, default: `gen_random_uuid()`, note: 'Primary identifier']
|
||||
name text [not null, unique, note: 'Property name/identifier (e.g., "membership_number")']
|
||||
value_type text [not null, note: 'Data type: string | integer | boolean | date | email']
|
||||
description text [null, note: 'Human-readable description']
|
||||
immutable boolean [not null, default: false, note: 'If true, value cannot be changed after creation']
|
||||
required boolean [not null, default: false, note: 'If true, all members must have this property']
|
||||
|
||||
indexes {
|
||||
name [unique, name: 'property_types_unique_name_index']
|
||||
}
|
||||
|
||||
Note: '''
|
||||
**Property Type Definitions**
|
||||
|
||||
Defines the schema and behavior for custom member properties.
|
||||
|
||||
**Attributes:**
|
||||
- `name`: Unique identifier for the property type
|
||||
- `value_type`: Enforces data type consistency
|
||||
- `description`: Documentation for users/admins
|
||||
- `immutable`: Prevents changes after initial creation (e.g., membership numbers)
|
||||
- `required`: Enforces that all members must have this property
|
||||
|
||||
**Constraints:**
|
||||
- `value_type` must be one of: string, integer, boolean, date, email
|
||||
- `name` must be unique across all property types
|
||||
- Cannot be deleted if properties reference it (ON DELETE RESTRICT)
|
||||
|
||||
**Examples:**
|
||||
- Membership Number (string, immutable, required)
|
||||
- Emergency Contact (string, mutable, optional)
|
||||
- Certified Trainer (boolean, mutable, optional)
|
||||
- Certification Date (date, immutable, optional)
|
||||
'''
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// RELATIONSHIPS
|
||||
// ============================================
|
||||
|
||||
// Optional 1:1 User ↔ Member Link
|
||||
// - A user can have 0 or 1 linked member (optional)
|
||||
// - A member can have 0 or 1 linked user (optional)
|
||||
// - Both can exist independently
|
||||
// - ON DELETE SET NULL: User preserved when member deleted
|
||||
// - Email Synchronization: When linking occurs, user.email becomes source of truth
|
||||
Ref: users.member_id - members.id [delete: set null]
|
||||
|
||||
// Member → Properties (1:N)
|
||||
// - One member can have multiple properties
|
||||
// - Each property belongs to exactly one member
|
||||
// - ON DELETE CASCADE: Properties deleted when member deleted
|
||||
// - UNIQUE constraint: One property per type per member
|
||||
Ref: properties.member_id > members.id [delete: cascade]
|
||||
|
||||
// Property → PropertyType (N:1)
|
||||
// - Many properties can reference one property type
|
||||
// - Property type defines the schema/behavior
|
||||
// - ON DELETE RESTRICT: Cannot delete type if properties exist
|
||||
Ref: properties.property_type_id > property_types.id [delete: restrict]
|
||||
|
||||
// ============================================
|
||||
// ENUMS
|
||||
// ============================================
|
||||
|
||||
// Valid data types for property values
|
||||
// Determines how Property.value is interpreted
|
||||
Enum property_value_type {
|
||||
string [note: 'Text data']
|
||||
integer [note: 'Numeric data']
|
||||
boolean [note: 'True/False flags']
|
||||
date [note: 'Date values']
|
||||
email [note: 'Validated email addresses']
|
||||
}
|
||||
|
||||
// Token purposes for different authentication flows
|
||||
Enum token_purpose {
|
||||
access [note: 'Short-lived access tokens']
|
||||
refresh [note: 'Long-lived refresh tokens']
|
||||
password_reset [note: 'Password reset tokens']
|
||||
email_confirmation [note: 'Email verification tokens']
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// TABLE GROUPS
|
||||
// ============================================
|
||||
|
||||
TableGroup accounts_domain {
|
||||
users
|
||||
tokens
|
||||
|
||||
Note: '''
|
||||
**Accounts Domain**
|
||||
|
||||
Handles user authentication and session management using AshAuthentication.
|
||||
Supports multiple authentication strategies (Password, OIDC).
|
||||
'''
|
||||
}
|
||||
|
||||
TableGroup membership_domain {
|
||||
members
|
||||
properties
|
||||
property_types
|
||||
|
||||
Note: '''
|
||||
**Membership Domain**
|
||||
|
||||
Core business logic for club membership management.
|
||||
Supports flexible, extensible member data model.
|
||||
'''
|
||||
}
|
||||
|
||||
1227
docs/development-progress-log.md
Normal file
1227
docs/development-progress-log.md
Normal file
File diff suppressed because it is too large
Load diff
743
docs/feature-roadmap.md
Normal file
743
docs/feature-roadmap.md
Normal file
|
|
@ -0,0 +1,743 @@
|
|||
# Feature Roadmap & Implementation Plan
|
||||
|
||||
**Project:** Mila - Membership Management System
|
||||
**Last Updated:** 2025-11-10
|
||||
**Status:** Planning Phase
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Phase 1: Feature Area Breakdown](#phase-1-feature-area-breakdown)
|
||||
2. [Phase 2: API Endpoint Definition](#phase-2-api-endpoint-definition)
|
||||
3. [Phase 3: Implementation Task Creation](#phase-3-implementation-task-creation)
|
||||
4. [Phase 4: Task Organization and Prioritization](#phase-4-task-organization-and-prioritization)
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Feature Area Breakdown
|
||||
|
||||
### Feature Areas
|
||||
|
||||
#### 1. **Authentication & Authorization** 🔐
|
||||
|
||||
**Current State:**
|
||||
- ✅ OIDC authentication (Rauthy)
|
||||
- ✅ Password-based authentication
|
||||
- ✅ User sessions and tokens
|
||||
- ✅ Basic authentication flows
|
||||
|
||||
**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)
|
||||
- [#144](https://git.local-it.org/local-it/mitgliederverwaltung/issues/144) - Add language switch dropdown to login screen (Low)
|
||||
|
||||
**Missing Features:**
|
||||
- ❌ Role-based access control (RBAC)
|
||||
- ❌ Permission system
|
||||
- ❌ Password reset flow
|
||||
- ❌ Email verification
|
||||
- ❌ Two-factor authentication (future)
|
||||
|
||||
**Related Issues:**
|
||||
- [#191](https://git.local-it.org/local-it/mitgliederverwaltung/issues/191) - Implement Roles in Ash (M)
|
||||
- [#190](https://git.local-it.org/local-it/mitgliederverwaltung/issues/190) - Implement Permissions in Ash (M)
|
||||
- [#151](https://git.local-it.org/local-it/mitgliederverwaltung/issues/151) - Define implementation plan for roles and permissions (M) [3/7 tasks done]
|
||||
|
||||
---
|
||||
|
||||
#### 2. **Member Management** 👥
|
||||
|
||||
**Current State:**
|
||||
- ✅ Member CRUD operations
|
||||
- ✅ Member profile with personal data
|
||||
- ✅ Address management
|
||||
- ✅ Membership status tracking
|
||||
- ✅ Full-text search (PostgreSQL tsvector)
|
||||
- ✅ Sorting by basic fields
|
||||
- ✅ User-Member linking (optional 1:1)
|
||||
- ✅ Email synchronization between User and Member
|
||||
|
||||
**Open Issues:**
|
||||
- [#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)
|
||||
- [#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)
|
||||
- [#154](https://git.local-it.org/local-it/mitgliederverwaltung/issues/154) - Concept advanced search (Low priority, needs refinement)
|
||||
|
||||
**Missing Features:**
|
||||
- ❌ Fuzzy search
|
||||
- ❌ Advanced filters (date ranges, multiple criteria)
|
||||
- ❌ Pagination (currently all members loaded)
|
||||
- ❌ Bulk operations (bulk delete, bulk update)
|
||||
- ❌ Member import/export (CSV, Excel)
|
||||
- ❌ Member profile photos/avatars
|
||||
- ❌ Member history/audit log
|
||||
- ❌ Duplicate detection
|
||||
|
||||
---
|
||||
|
||||
#### 3. **Custom Fields (Property System)** 🔧
|
||||
|
||||
**Current State:**
|
||||
- ✅ Property types (string, integer, boolean, date, email)
|
||||
- ✅ Property type management
|
||||
- ✅ Dynamic property assignment to members
|
||||
- ✅ Union type storage (JSONB)
|
||||
|
||||
**Open Issues:**
|
||||
- [#194](https://git.local-it.org/local-it/mitgliederverwaltung/issues/194) - Custom Fields: Harden implementation (S) [0/3 tasks]
|
||||
- [#157](https://git.local-it.org/local-it/mitgliederverwaltung/issues/157) - Concept how custom fields are handled (M, High priority) [0/4 tasks]
|
||||
- [#161](https://git.local-it.org/local-it/mitgliederverwaltung/issues/161) - Don't show birthday field for default configurations (S, Low priority)
|
||||
- [#153](https://git.local-it.org/local-it/mitgliederverwaltung/issues/153) - Sorting functionalities for custom fields (M, Low priority)
|
||||
|
||||
**Missing Features:**
|
||||
- ❌ Default field visibility configuration
|
||||
- ❌ Field groups/categories
|
||||
- ❌ Conditional fields (show field X if field Y = value)
|
||||
- ❌ Field validation rules (min/max, regex patterns)
|
||||
- ❌ Required custom fields
|
||||
- ❌ Multi-select fields
|
||||
- ❌ File upload fields
|
||||
- ❌ Sorting by custom fields
|
||||
- ❌ Searching by custom fields
|
||||
|
||||
---
|
||||
|
||||
#### 4. **User Management** 👤
|
||||
|
||||
**Current State:**
|
||||
- ✅ User CRUD operations
|
||||
- ✅ User list view
|
||||
- ✅ User profile view
|
||||
- ✅ Admin password setting
|
||||
- ✅ User-Member relationship
|
||||
|
||||
**Missing Features:**
|
||||
- ❌ User roles assignment UI
|
||||
- ❌ User permissions management
|
||||
- ❌ User activity log
|
||||
- ❌ User invitation system
|
||||
- ❌ User onboarding flow
|
||||
- ❌ Self-service profile editing
|
||||
- ❌ Password change flow
|
||||
|
||||
---
|
||||
|
||||
#### 5. **Navigation & UX** 🧭
|
||||
|
||||
**Current State:**
|
||||
- ✅ Basic navigation structure
|
||||
- ✅ Navbar with profile button
|
||||
- ✅ Member list as landing page
|
||||
- ✅ Breadcrumbs (basic)
|
||||
|
||||
**Open Issues:**
|
||||
- [#188](https://git.local-it.org/local-it/mitgliederverwaltung/issues/188) - Check if searching just on typing is accessible (S, Low priority)
|
||||
- [#174](https://git.local-it.org/local-it/mitgliederverwaltung/issues/174) - Accessibility - aria-sort in tables (S, Low priority)
|
||||
|
||||
**Missing Features:**
|
||||
- ❌ Dashboard/Home page
|
||||
- ❌ Quick actions menu
|
||||
- ❌ Recent activity widget
|
||||
- ❌ Keyboard shortcuts
|
||||
- ❌ Mobile navigation
|
||||
- ❌ Context-sensitive help
|
||||
- ❌ Onboarding tooltips
|
||||
|
||||
---
|
||||
|
||||
#### 6. **Internationalization (i18n)** 🌍
|
||||
|
||||
**Current State:**
|
||||
- ✅ Gettext integration
|
||||
- ✅ German translations
|
||||
- ✅ English translations
|
||||
- ✅ Translation files for auth, errors, default
|
||||
|
||||
**Open Issues:**
|
||||
- [#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)
|
||||
|
||||
**Missing Features:**
|
||||
- ❌ Language switcher UI
|
||||
- ❌ User-specific language preferences
|
||||
- ❌ Date/time localization
|
||||
- ❌ Number formatting (currency, decimals)
|
||||
- ❌ Complete translation coverage
|
||||
- ❌ RTL support (future)
|
||||
|
||||
---
|
||||
|
||||
#### 7. **Payment & Fees Management** 💰
|
||||
|
||||
**Current State:**
|
||||
- ✅ Basic "paid" boolean field on members
|
||||
- ⚠️ No payment tracking
|
||||
|
||||
**Open Issues:**
|
||||
- [#156](https://git.local-it.org/local-it/mitgliederverwaltung/issues/156) - Set up & document testing environment for vereinfacht.digital (L, Low priority)
|
||||
|
||||
**Missing Features:**
|
||||
- ❌ Membership fee configuration
|
||||
- ❌ Payment records/transactions
|
||||
- ❌ Payment history per member
|
||||
- ❌ Payment reminders
|
||||
- ❌ Payment status tracking (pending, paid, overdue)
|
||||
- ❌ Invoice generation
|
||||
- ❌ vereinfacht.digital API integration
|
||||
- ❌ SEPA direct debit support
|
||||
- ❌ Payment reports
|
||||
|
||||
**Related Milestones:**
|
||||
- Import transactions via vereinfacht API
|
||||
|
||||
---
|
||||
|
||||
#### 8. **Admin Panel & Configuration** ⚙️
|
||||
|
||||
**Current State:**
|
||||
- ✅ AshAdmin integration (basic)
|
||||
- ⚠️ No user-facing admin UI
|
||||
|
||||
**Open Issues:**
|
||||
- [#186](https://git.local-it.org/local-it/mitgliederverwaltung/issues/186) - Create Architecture docs in Repo (S, Low priority)
|
||||
|
||||
**Missing Features:**
|
||||
- ❌ Global settings management
|
||||
- ❌ Club/Organization profile
|
||||
- ❌ Email templates configuration
|
||||
- ❌ Property type management UI (user-facing)
|
||||
- ❌ Role and permission management UI
|
||||
- ❌ System health dashboard
|
||||
- ❌ Audit log viewer
|
||||
- ❌ Backup/restore functionality
|
||||
|
||||
**Related Milestones:**
|
||||
- As Admin I can configure settings globally
|
||||
|
||||
---
|
||||
|
||||
#### 9. **Communication & Notifications** 📧
|
||||
|
||||
**Current State:**
|
||||
- ✅ Swoosh mailer integration
|
||||
- ✅ Email confirmation (via AshAuthentication)
|
||||
- ✅ Password reset emails (via AshAuthentication)
|
||||
- ⚠️ No member communication features
|
||||
|
||||
**Missing Features:**
|
||||
- ❌ Email broadcast to members
|
||||
- ❌ Email templates (customizable)
|
||||
- ❌ Email to member groups/filters
|
||||
|
||||
---
|
||||
|
||||
#### 10. **Reporting & Analytics** 📊
|
||||
|
||||
**Current State:**
|
||||
- ❌ No reporting features
|
||||
|
||||
**Missing Features:**
|
||||
- ❌ Member statistics dashboard
|
||||
- ❌ Membership growth charts
|
||||
- ❌ Payment reports
|
||||
- ❌ Custom report builder
|
||||
- ❌ Export to PDF/CSV/Excel
|
||||
- ❌ Scheduled reports
|
||||
- ❌ Data visualization
|
||||
|
||||
---
|
||||
|
||||
#### 11. **Data Import/Export** 📥📤
|
||||
|
||||
**Current State:**
|
||||
- ✅ Seed data script
|
||||
- ⚠️ No user-facing import/export
|
||||
|
||||
**Missing Features:**
|
||||
- ❌ CSV import for members
|
||||
- ❌ Excel import for members
|
||||
- ❌ Import validation and preview
|
||||
- ❌ Import error handling
|
||||
- ❌ Bulk data export
|
||||
- ❌ Backup export
|
||||
- ❌ Data migration tools
|
||||
|
||||
---
|
||||
|
||||
#### 12. **Testing & Quality Assurance** 🧪
|
||||
|
||||
**Current State:**
|
||||
- ✅ ExUnit test suite
|
||||
- ✅ Unit tests for resources
|
||||
- ✅ Integration tests for email sync
|
||||
- ✅ LiveView tests
|
||||
- ✅ Component tests
|
||||
- ✅ CI/CD pipeline (Drone)
|
||||
|
||||
**Missing Features:**
|
||||
- ❌ E2E tests (browser automation)
|
||||
- ❌ Performance testing
|
||||
- ❌ Load testing
|
||||
- ❌ Security penetration testing
|
||||
- ❌ Accessibility testing automation
|
||||
- ❌ Visual regression testing
|
||||
- ❌ Test coverage reporting
|
||||
|
||||
---
|
||||
|
||||
#### 13. **Infrastructure & DevOps** 🚀
|
||||
|
||||
**Current State:**
|
||||
- ✅ Docker Compose for development
|
||||
- ✅ Production Dockerfile
|
||||
- ✅ Drone CI/CD pipeline
|
||||
- ✅ Renovate for dependency updates
|
||||
- ⚠️ No staging environment
|
||||
|
||||
**Open Issues:**
|
||||
- [#186](https://git.local-it.org/local-it/mitgliederverwaltung/issues/186) - Create Architecture docs in Repo (S, Low priority)
|
||||
|
||||
**Missing Features:**
|
||||
- ❌ Staging environment
|
||||
- ❌ Automated deployment
|
||||
- ❌ Database backup automation
|
||||
- ❌ Monitoring and alerting
|
||||
- ❌ Error tracking (Sentry, etc.)
|
||||
- ❌ Log aggregation
|
||||
- ❌ Health checks and uptime monitoring
|
||||
|
||||
**Related Milestones:**
|
||||
- We have a staging environment
|
||||
- We implement security measures
|
||||
|
||||
---
|
||||
|
||||
#### 14. **Security & Compliance** 🔒
|
||||
|
||||
**Current State:**
|
||||
- ✅ OIDC authentication
|
||||
- ✅ Password hashing (bcrypt)
|
||||
- ✅ CSRF protection
|
||||
- ✅ SQL injection prevention (Ecto)
|
||||
- ✅ Sobelow security scans
|
||||
- ✅ Dependency auditing
|
||||
|
||||
**Missing Features:**
|
||||
- ❌ Role-based access control (see #1)
|
||||
- ❌ Audit logging
|
||||
- ❌ GDPR compliance features (data export, deletion)
|
||||
- ❌ Session management (timeout, concurrent sessions)
|
||||
- ❌ Rate limiting
|
||||
- ❌ IP whitelisting/blacklisting
|
||||
- ❌ Security headers configuration
|
||||
- ❌ Data retention policies
|
||||
|
||||
**Related Milestones:**
|
||||
- We implement security measures
|
||||
|
||||
---
|
||||
|
||||
#### 15. **Accessibility & Usability** ♿
|
||||
|
||||
**Current State:**
|
||||
- ✅ Semantic HTML
|
||||
- ✅ Basic ARIA labels
|
||||
- ⚠️ Needs comprehensive audit
|
||||
|
||||
**Open Issues:**
|
||||
- [#188](https://git.local-it.org/local-it/mitgliederverwaltung/issues/188) - Check if searching just on typing is accessible (S, Low priority)
|
||||
- [#174](https://git.local-it.org/local-it/mitgliederverwaltung/issues/174) - Accessibility - aria-sort in tables (S, Low priority)
|
||||
|
||||
**Missing Features:**
|
||||
- ❌ Comprehensive accessibility audit (WCAG 2.1 Level AA)
|
||||
- ❌ Keyboard navigation improvements
|
||||
- ❌ Screen reader optimization
|
||||
- ❌ High contrast mode
|
||||
- ❌ Font size adjustments
|
||||
- ❌ Focus management
|
||||
- ❌ Skip links
|
||||
- ❌ Error announcements
|
||||
|
||||
---
|
||||
|
||||
### Feature Area Summary
|
||||
|
||||
| Feature Area | Current Status | Priority | Complexity |
|
||||
|--------------|----------------|----------|------------|
|
||||
| **Authentication & Authorization** | 40% complete | **High** | Medium |
|
||||
| **Member Management** | 70% complete | **High** | Low-Medium |
|
||||
| **Custom Fields** | 50% complete | **High** | Medium |
|
||||
| **User Management** | 60% complete | Medium | Low |
|
||||
| **Navigation & UX** | 50% complete | Medium | Low |
|
||||
| **Internationalization** | 70% complete | Low | Low |
|
||||
| **Payment & Fees** | 5% complete | **High** | High |
|
||||
| **Admin Panel** | 20% complete | Medium | Medium |
|
||||
| **Communication** | 30% complete | Medium | Medium |
|
||||
| **Reporting** | 0% complete | Medium | Medium-High |
|
||||
| **Import/Export** | 10% complete | Low | Medium |
|
||||
| **Testing & QA** | 60% complete | Medium | Low-Medium |
|
||||
| **Infrastructure** | 70% complete | Medium | Medium |
|
||||
| **Security** | 50% complete | **High** | Medium-High |
|
||||
| **Accessibility** | 40% complete | Medium | Medium |
|
||||
|
||||
---
|
||||
|
||||
### Open Milestones (From Issues)
|
||||
|
||||
1. ✅ **Ich kann einen neuen Kontakt anlegen** (Closed)
|
||||
2. 🔄 **I can search through the list of members - fulltext** (Open) - Related: #162, #154
|
||||
3. 🔄 **I can sort the list of members for specific fields** (Open) - Related: #153
|
||||
4. 🔄 **We have a intuitive navigation structure** (Open)
|
||||
5. 🔄 **We have different roles and permissions** (Open) - Related: #191, #190, #151
|
||||
6. 🔄 **As Admin I can configure settings globally** (Open)
|
||||
7. 🔄 **Accounts & Logins** (Open) - Related: #171, #169, #168
|
||||
8. 🔄 **I can add custom fields** (Open) - Related: #194, #157, #161
|
||||
9. 🔄 **Import transactions via vereinfacht API** (Open) - Related: #156
|
||||
10. 🔄 **We have a staging environment** (Open)
|
||||
11. 🔄 **We implement security measures** (Open)
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: API Endpoint Definition
|
||||
|
||||
### Endpoint Types
|
||||
|
||||
Since this is a **Phoenix LiveView** application with **Ash Framework**, we have three types of endpoints:
|
||||
|
||||
1. **LiveView Endpoints** - Mount points and event handlers
|
||||
2. **HTTP Controller Endpoints** - Traditional REST-style endpoints
|
||||
3. **Ash Resource Actions** - Backend data layer API
|
||||
|
||||
### Authentication Requirements Legend
|
||||
|
||||
- 🔓 **Public** - No authentication required
|
||||
- 🔐 **Authenticated** - Requires valid user session
|
||||
- 👤 **User Role** - Requires specific user role
|
||||
- 🛡️ **Admin Only** - Requires admin privileges
|
||||
|
||||
---
|
||||
|
||||
### 1. Authentication & Authorization Endpoints
|
||||
|
||||
#### HTTP Controller Endpoints
|
||||
|
||||
| Method | Route | Purpose | Auth | Request | Response |
|
||||
|--------|-------|---------|------|---------|----------|
|
||||
| `GET` | `/auth/user/password/sign_in` | Show password login form | 🔓 | - | HTML form |
|
||||
| `POST` | `/auth/user/password/sign_in` | Submit password login | 🔓 | `{email, password}` | Redirect + session cookie |
|
||||
| `GET` | `/auth/user/rauthy` | Initiate OIDC flow | 🔓 | - | Redirect to Rauthy |
|
||||
| `GET` | `/auth/user/rauthy/callback` | Handle OIDC callback | 🔓 | `{code, state}` | Redirect + session cookie |
|
||||
| `POST` | `/auth/user/sign_out` | Sign out user | 🔐 | - | Redirect to login |
|
||||
| `GET` | `/auth/user/password/reset` | Show password reset form | 🔓 | - | HTML form |
|
||||
| `POST` | `/auth/user/password/reset` | Request password reset | 🔓 | `{email}` | Success message + email sent |
|
||||
| `GET` | `/auth/user/password/reset/:token` | Show reset password form | 🔓 | - | HTML form |
|
||||
| `POST` | `/auth/user/password/reset/:token` | Submit new password | 🔓 | `{password, password_confirmation}` | Redirect to login |
|
||||
|
||||
#### Ash Resource Actions
|
||||
|
||||
| Resource | Action | Purpose | Auth | Input | Output |
|
||||
|----------|--------|---------|------|-------|--------|
|
||||
| `User` | `:sign_in_with_password` | Password authentication | 🔓 | `{email, password}` | `{:ok, user}` or `{:error, reason}` |
|
||||
| `User` | `:sign_in_with_rauthy` | OIDC authentication | 🔓 | `{oidc_id, email, user_info}` | `{:ok, user}` or `{:error, reason}` |
|
||||
| `User` | `:register_with_password` | Create user with password | 🔓 | `{email, password}` | `{:ok, user}` |
|
||||
| `User` | `:register_with_rauthy` | Create user via OIDC | 🔓 | `{oidc_id, email}` | `{:ok, user}` |
|
||||
| `User` | `:request_password_reset` | Generate reset token | 🔓 | `{email}` | `{:ok, token}` |
|
||||
| `User` | `:reset_password` | Reset password with token | 🔓 | `{token, password}` | `{:ok, user}` |
|
||||
| `Token` | `:revoke` | Revoke authentication token | 🔐 | `{jti}` | `{:ok, token}` |
|
||||
|
||||
#### **NEW: Role & Permission Actions** (Issue #191, #190, #151)
|
||||
|
||||
| Resource | Action | Purpose | Auth | Input | Output |
|
||||
|----------|--------|---------|------|-------|--------|
|
||||
| `Role` | `:create` | Create new role | 🛡️ | `{name, description, permissions}` | `{:ok, role}` |
|
||||
| `Role` | `:list` | List all roles | 🔐 | - | `[%Role{}]` |
|
||||
| `Role` | `:update` | Update role | 🛡️ | `{id, name, permissions}` | `{:ok, role}` |
|
||||
| `Role` | `:delete` | Delete role | 🛡️ | `{id}` | `{:ok, role}` |
|
||||
| `User` | `:assign_role` | Assign role to user | 🛡️ | `{user_id, role_id}` | `{:ok, user}` |
|
||||
| `User` | `:remove_role` | Remove role from user | 🛡️ | `{user_id, role_id}` | `{:ok, user}` |
|
||||
| `Permission` | `:list` | List all permissions | 🔐 | - | `[%Permission{}]` |
|
||||
| `Permission` | `:check` | Check user permission | 🔐 | `{user_id, resource, action}` | `{:ok, boolean}` |
|
||||
|
||||
---
|
||||
|
||||
### 2. Member Management Endpoints
|
||||
|
||||
#### LiveView Endpoints
|
||||
|
||||
| Mount | Purpose | Auth | Query Params | Events |
|
||||
|-------|---------|------|--------------|--------|
|
||||
| `/members` | Member list with search/sort | 🔐 | `?search=&sort_by=&sort_dir=` | `search`, `sort`, `delete`, `select` |
|
||||
| `/members/new` | Create new member form | 🔐 | - | `save`, `cancel`, `add_property` |
|
||||
| `/members/:id` | Member detail view | 🔐 | - | `edit`, `delete`, `link_user` |
|
||||
| `/members/:id/edit` | Edit member form | 🔐 | - | `save`, `cancel`, `add_property`, `remove_property` |
|
||||
|
||||
#### LiveView Event Handlers
|
||||
|
||||
| Event | Purpose | Params | Response |
|
||||
|-------|---------|--------|----------|
|
||||
| `search` | Trigger search | `%{"search" => query}` | Update member list |
|
||||
| `sort` | Sort member list | `%{"field" => field}` | Update sorted list |
|
||||
| `delete` | Delete member | `%{"id" => id}` | Redirect to list |
|
||||
| `save` | Create/update member | `%{"member" => attrs}` | Redirect or show errors |
|
||||
| `link_user` | Link user to member | `%{"user_id" => id}` | Update member view |
|
||||
| `unlink_user` | Unlink user from member | - | Update member view |
|
||||
| `add_property` | Add custom property | `%{"property_type_id" => id, "value" => val}` | Update form |
|
||||
| `remove_property` | Remove custom property | `%{"property_id" => id}` | Update form |
|
||||
|
||||
#### Ash Resource Actions
|
||||
|
||||
| Resource | Action | Purpose | Auth | Input | Output |
|
||||
|----------|--------|---------|------|-------|--------|
|
||||
| `Member` | `:create_member` | Create member | 🔐 | `{first_name, last_name, email, ...}` | `{:ok, member}` |
|
||||
| `Member` | `:read` | List/search members | 🔐 | `{search, sort_by, limit, offset}` | `[%Member{}]` |
|
||||
| `Member` | `:update_member` | Update member | 🔐 | `{id, attrs}` | `{:ok, member}` |
|
||||
| `Member` | `:destroy` | Delete member | 🔐 | `{id}` | `{:ok, member}` |
|
||||
| `Member` | `:search_fulltext` | Full-text search | 🔐 | `{query}` | `[%Member{}]` |
|
||||
| `Member` | `:link_to_user` | Link member to user | 🔐 | `{member_id, user_id}` | `{:ok, member}` |
|
||||
| `Member` | `:unlink_from_user` | Unlink from user | 🔐 | `{member_id}` | `{:ok, member}` |
|
||||
|
||||
#### **NEW: Enhanced Search & Filter Actions** (Issue #162, #154, #165)
|
||||
|
||||
| Resource | Action | Purpose | Auth | Input | Output |
|
||||
|----------|--------|---------|------|-------|--------|
|
||||
| `Member` | `:fuzzy_search` | Fuzzy text search | 🔐 | `{query, threshold}` | `[%Member{}]` |
|
||||
| `Member` | `:advanced_search` | Multi-criteria search | 🔐 | `{filters: [{field, op, value}]}` | `[%Member{}]` |
|
||||
| `Member` | `:paginate` | Paginated member list | 🔐 | `{page, per_page, filters}` | `{members, total, page_info}` |
|
||||
| `Member` | `:sort_by_custom_field` | Sort by property | 🔐 | `{property_type_id, direction}` | `[%Member{}]` |
|
||||
| `Member` | `:bulk_delete` | Delete multiple members | 🛡️ | `{ids: [id1, id2, ...]}` | `{:ok, count}` |
|
||||
| `Member` | `:bulk_update` | Update multiple members | 🛡️ | `{ids, attrs}` | `{:ok, count}` |
|
||||
| `Member` | `:export` | Export to CSV/Excel | 🔐 | `{format, filters}` | File download |
|
||||
| `Member` | `:import` | Import from CSV | 🛡️ | `{file, mapping}` | `{:ok, imported_count, errors}` |
|
||||
|
||||
---
|
||||
|
||||
### 3. Custom Fields (Property System) Endpoints
|
||||
|
||||
#### LiveView Endpoints
|
||||
|
||||
| Mount | Purpose | Auth | Events |
|
||||
|-------|---------|------|--------|
|
||||
| `/property-types` | List property types | 🛡️ | `new`, `edit`, `delete` |
|
||||
| `/property-types/new` | Create property type | 🛡️ | `save`, `cancel` |
|
||||
| `/property-types/:id/edit` | Edit property type | 🛡️ | `save`, `cancel`, `delete` |
|
||||
|
||||
#### Ash Resource Actions
|
||||
|
||||
| Resource | Action | Purpose | Auth | Input | Output |
|
||||
|----------|--------|---------|------|-------|--------|
|
||||
| `PropertyType` | `:create` | Create property type | 🛡️ | `{name, value_type, description, ...}` | `{:ok, property_type}` |
|
||||
| `PropertyType` | `:read` | List property types | 🔐 | - | `[%PropertyType{}]` |
|
||||
| `PropertyType` | `:update` | Update property type | 🛡️ | `{id, attrs}` | `{:ok, property_type}` |
|
||||
| `PropertyType` | `:destroy` | Delete property type | 🛡️ | `{id}` | `{:ok, property_type}` |
|
||||
| `Property` | `:create` | Add property to member | 🔐 | `{member_id, property_type_id, value}` | `{:ok, property}` |
|
||||
| `Property` | `:update` | Update property value | 🔐 | `{id, value}` | `{:ok, property}` |
|
||||
| `Property` | `:destroy` | Remove property | 🔐 | `{id}` | `{:ok, property}` |
|
||||
|
||||
#### **NEW: Enhanced Custom Fields** (Issue #194, #157, #161, #153)
|
||||
|
||||
| Resource | Action | Purpose | Auth | Input | Output |
|
||||
|----------|--------|---------|------|-------|--------|
|
||||
| `PropertyType` | `:set_default_visibility` | Show/hide by default | 🛡️ | `{id, visible}` | `{:ok, property_type}` |
|
||||
| `PropertyType` | `:set_required` | Mark as required | 🛡️ | `{id, required}` | `{:ok, property_type}` |
|
||||
| `PropertyType` | `:add_validation` | Add validation rule | 🛡️ | `{id, rule_type, params}` | `{:ok, property_type}` |
|
||||
| `PropertyType` | `:create_group` | Create field group | 🛡️ | `{name, property_type_ids}` | `{:ok, group}` |
|
||||
| `Property` | `:validate_value` | Validate property value | 🔐 | `{property_type_id, value}` | `{:ok, valid}` or `{:error, reason}` |
|
||||
|
||||
---
|
||||
|
||||
### 4. User Management Endpoints
|
||||
|
||||
#### LiveView Endpoints
|
||||
|
||||
| Mount | Purpose | Auth | Events |
|
||||
|-------|---------|------|--------|
|
||||
| `/users` | User list | 🛡️ | `new`, `edit`, `delete`, `assign_role` |
|
||||
| `/users/new` | Create user form | 🛡️ | `save`, `cancel` |
|
||||
| `/users/:id` | User detail view | 🔐 | `edit`, `delete`, `change_password` |
|
||||
| `/users/:id/edit` | Edit user form | 🔐 | `save`, `cancel`, `link_member` |
|
||||
| `/profile` | Current user profile | 🔐 | `edit`, `change_password` |
|
||||
|
||||
#### Ash Resource Actions
|
||||
|
||||
| Resource | Action | Purpose | Auth | Input | Output |
|
||||
|----------|--------|---------|------|-------|--------|
|
||||
| `User` | `:create_user` | Create user (admin) | 🛡️ | `{email, member_id?}` | `{:ok, user}` |
|
||||
| `User` | `:read` | List users | 🛡️ | - | `[%User{}]` |
|
||||
| `User` | `:update_user` | Update user | 🔐 | `{id, email, member_id?}` | `{:ok, user}` |
|
||||
| `User` | `:destroy` | Delete user | 🛡️ | `{id}` | `{:ok, user}` |
|
||||
| `User` | `:admin_set_password` | Set password (admin) | 🛡️ | `{id, password}` | `{:ok, user}` |
|
||||
| `User` | `:change_password` | Change own password | 🔐 | `{current_password, new_password}` | `{:ok, user}` |
|
||||
|
||||
#### **NEW: Combined User/Member Management** (Issue #169, #168)
|
||||
|
||||
| Resource | Action | Purpose | Auth | Input | Output |
|
||||
|----------|--------|---------|------|-------|--------|
|
||||
| `User` | `:create_with_member` | Create user + member together | 🛡️ | `{user: {...}, member: {...}}` | `{:ok, %{user, member}}` |
|
||||
| `User` | `:invite_user` | Send invitation email | 🛡️ | `{email, role_id, member_id?}` | `{:ok, invitation}` |
|
||||
| `User` | `:accept_invitation` | Accept invitation | 🔓 | `{token, password}` | `{:ok, user}` |
|
||||
|
||||
---
|
||||
|
||||
### 5. Navigation & UX Endpoints
|
||||
|
||||
#### LiveView Endpoints
|
||||
|
||||
| Mount | Purpose | Auth | Events |
|
||||
|-------|---------|------|--------|
|
||||
| `/` | Dashboard/Home | 🔐 | - |
|
||||
| `/dashboard` | Dashboard view | 🔐 | Contextual based on role |
|
||||
|
||||
#### HTTP Controller Endpoints
|
||||
|
||||
| Method | Route | Purpose | Auth | Request | Response |
|
||||
|--------|-------|---------|------|---------|----------|
|
||||
| `GET` | `/health` | Health check | 🔓 | - | `{"status": "ok"}` |
|
||||
| `GET` | `/` | Root redirect | - | - | Redirect to dashboard or login |
|
||||
|
||||
---
|
||||
|
||||
### 6. Internationalization Endpoints
|
||||
|
||||
#### HTTP Controller Endpoints
|
||||
|
||||
| Method | Route | Purpose | Auth | Request | Response |
|
||||
|--------|-------|---------|------|---------|----------|
|
||||
| `POST` | `/locale` | Set user locale | 🔐 | `{locale: "de"}` | Redirect with cookie |
|
||||
| `GET` | `/locales` | List available locales | 🔓 | - | `["de", "en"]` |
|
||||
|
||||
---
|
||||
|
||||
### 7. Payment & Fees Management Endpoints
|
||||
|
||||
#### LiveView Endpoints (NEW - Issue #156)
|
||||
|
||||
| Mount | Purpose | Auth | Events |
|
||||
|-------|---------|------|--------|
|
||||
| `/payments` | Payment list | 🔐 | `new`, `record_payment`, `send_reminder` |
|
||||
| `/payments/:id` | Payment detail | 🔐 | `edit`, `delete`, `mark_paid` |
|
||||
| `/fees` | Fee configuration | 🛡️ | `create`, `edit`, `delete` |
|
||||
| `/invoices` | Invoice list | 🔐 | `generate`, `download`, `send` |
|
||||
|
||||
#### Ash Resource Actions (NEW)
|
||||
|
||||
| Resource | Action | Purpose | Auth | Input | Output |
|
||||
|----------|--------|---------|------|-------|--------|
|
||||
| `Fee` | `:create` | Create fee type | 🛡️ | `{name, amount, frequency}` | `{:ok, fee}` |
|
||||
| `Fee` | `:read` | List fees | 🔐 | - | `[%Fee{}]` |
|
||||
| `Payment` | `:create` | Record payment | 🔐 | `{member_id, fee_id, amount, date}` | `{:ok, payment}` |
|
||||
| `Payment` | `:list_by_member` | Member payment history | 🔐 | `{member_id}` | `[%Payment{}]` |
|
||||
| `Payment` | `:mark_paid` | Mark as paid | 🔐 | `{id}` | `{:ok, payment}` |
|
||||
| `Invoice` | `:generate` | Generate invoice | 🔐 | `{member_id, fee_id, period}` | `{:ok, invoice}` |
|
||||
| `Invoice` | `:send` | Send invoice via email | 🔐 | `{id}` | `{:ok, sent}` |
|
||||
| `Payment` | `:import_vereinfacht` | Import from vereinfacht.digital | 🛡️ | `{transactions}` | `{:ok, count}` |
|
||||
|
||||
---
|
||||
|
||||
### 8. Admin Panel & Configuration Endpoints
|
||||
|
||||
#### LiveView Endpoints (NEW)
|
||||
|
||||
| Mount | Purpose | Auth | Events |
|
||||
|-------|---------|------|--------|
|
||||
| `/admin` | Admin dashboard | 🛡️ | - |
|
||||
| `/admin/settings` | Global settings | 🛡️ | `save` |
|
||||
| `/admin/organization` | Organization profile | 🛡️ | `save` |
|
||||
| `/admin/email-templates` | Email template editor | 🛡️ | `create`, `edit`, `preview` |
|
||||
| `/admin/audit-log` | System audit log | 🛡️ | `filter`, `export` |
|
||||
|
||||
#### Ash Resource Actions (NEW)
|
||||
|
||||
| Resource | Action | Purpose | Auth | Input | Output |
|
||||
|----------|--------|---------|------|-------|--------|
|
||||
| `Setting` | `:get` | Get setting value | 🔐 | `{key}` | `value` |
|
||||
| `Setting` | `:set` | Set setting value | 🛡️ | `{key, value}` | `{:ok, setting}` |
|
||||
| `Setting` | `:list` | List all settings | 🛡️ | - | `[%Setting{}]` |
|
||||
| `Organization` | `:read` | Get organization info | 🔐 | - | `%Organization{}` |
|
||||
| `Organization` | `:update` | Update organization | 🛡️ | `{name, logo, ...}` | `{:ok, org}` |
|
||||
| `AuditLog` | `:list` | List audit entries | 🛡️ | `{filters, pagination}` | `[%AuditLog{}]` |
|
||||
|
||||
---
|
||||
|
||||
### 9. Communication & Notifications Endpoints
|
||||
|
||||
#### LiveView Endpoints (NEW)
|
||||
|
||||
| Mount | Purpose | Auth | Events |
|
||||
|-------|---------|------|--------|
|
||||
| `/communications` | Communication history | 🔐 | `new`, `view` |
|
||||
| `/communications/new` | Create email broadcast | 🔐 | `select_recipients`, `preview`, `send` |
|
||||
| `/notifications` | User notifications | 🔐 | `mark_read`, `mark_all_read` |
|
||||
|
||||
#### Ash Resource Actions (NEW)
|
||||
|
||||
| Resource | Action | Purpose | Auth | Input | Output |
|
||||
|----------|--------|---------|------|-------|--------|
|
||||
| `EmailBroadcast` | `:create` | Create broadcast | 🔐 | `{subject, body, recipient_filter}` | `{:ok, broadcast}` |
|
||||
| `EmailBroadcast` | `:send` | Send broadcast | 🔐 | `{id}` | `{:ok, sent_count}` |
|
||||
| `EmailTemplate` | `:create` | Create template | 🛡️ | `{name, subject, body}` | `{:ok, template}` |
|
||||
| `EmailTemplate` | `:render` | Render template | 🔐 | `{id, variables}` | `rendered_html` |
|
||||
| `Notification` | `:create` | Create notification | System | `{user_id, type, message}` | `{:ok, notification}` |
|
||||
| `Notification` | `:list_for_user` | Get user notifications | 🔐 | `{user_id}` | `[%Notification{}]` |
|
||||
| `Notification` | `:mark_read` | Mark as read | 🔐 | `{id}` | `{:ok, notification}` |
|
||||
|
||||
---
|
||||
|
||||
### 10. Reporting & Analytics Endpoints
|
||||
|
||||
#### LiveView Endpoints (NEW)
|
||||
|
||||
| Mount | Purpose | Auth | Events |
|
||||
|-------|---------|------|--------|
|
||||
| `/reports` | Reports dashboard | 🔐 | `generate`, `schedule` |
|
||||
| `/reports/members` | Member statistics | 🔐 | `filter`, `export` |
|
||||
| `/reports/payments` | Payment reports | 🔐 | `filter`, `export` |
|
||||
| `/reports/custom` | Custom report builder | 🛡️ | `build`, `save`, `run` |
|
||||
|
||||
#### Ash Resource Actions (NEW)
|
||||
|
||||
| Resource | Action | Purpose | Auth | Input | Output |
|
||||
|----------|--------|---------|------|-------|--------|
|
||||
| `Report` | `:generate_member_stats` | Member statistics | 🔐 | `{date_range, filters}` | Statistics object |
|
||||
| `Report` | `:generate_payment_stats` | Payment statistics | 🔐 | `{date_range}` | Statistics object |
|
||||
| `Report` | `:export_to_csv` | Export report to CSV | 🔐 | `{report_type, filters}` | CSV file |
|
||||
| `Report` | `:export_to_pdf` | Export report to PDF | 🔐 | `{report_type, filters}` | PDF file |
|
||||
| `Report` | `:schedule` | Schedule recurring report | 🛡️ | `{report_type, frequency, recipients}` | `{:ok, schedule}` |
|
||||
|
||||
---
|
||||
|
||||
### 11. Data Import/Export Endpoints
|
||||
|
||||
#### LiveView Endpoints (NEW)
|
||||
|
||||
| Mount | Purpose | Auth | Events |
|
||||
|-------|---------|------|--------|
|
||||
| `/import` | Data import wizard | 🛡️ | `upload`, `map_fields`, `preview`, `import` |
|
||||
| `/export` | Data export tool | 🔐 | `select_data`, `configure`, `export` |
|
||||
|
||||
#### Ash Resource Actions (NEW)
|
||||
|
||||
| Resource | Action | Purpose | Auth | Input | Output |
|
||||
|----------|--------|---------|------|-------|--------|
|
||||
| `Member` | `:import_csv` | Import members from CSV | 🛡️ | `{file, field_mapping}` | `{:ok, imported, errors}` |
|
||||
| `Member` | `:validate_import` | Validate import data | 🛡️ | `{file, field_mapping}` | `{:ok, validation_results}` |
|
||||
| `Member` | `:export_csv` | Export members to CSV | 🔐 | `{filters}` | CSV file |
|
||||
| `Member` | `:export_excel` | Export members to Excel | 🔐 | `{filters}` | Excel file |
|
||||
| `Database` | `:export_backup` | Full database backup | 🛡️ | - | Backup file |
|
||||
| `Database` | `:import_backup` | Restore from backup | 🛡️ | `{file}` | `{:ok, restored}` |
|
||||
|
||||
---
|
||||
|
||||
|
||||
---
|
||||
|
||||
**References:**
|
||||
- Open Issues: https://git.local-it.org/local-it/mitgliederverwaltung/issues
|
||||
- Project Board: Sprint 8 (23.10 - 13.11)
|
||||
- Architecture: See [`CODE_GUIDELINES.md`](../CODE_GUIDELINES.md)
|
||||
- Database Schema: See [`database-schema-readme.md`](database-schema-readme.md)
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue