mirror of
https://github.com/wekan/wekan.git
synced 2026-01-21 08:46:09 +01:00
472 lines
18 KiB
Markdown
472 lines
18 KiB
Markdown
# Wekan Persistence Audit Report
|
|
|
|
## Overview
|
|
This document audits the persistence mechanisms for Wekan board data, including swimlanes, lists, cards, checklists, and their properties (order, color, background, titles, etc.), as well as per-user settings.
|
|
|
|
---
|
|
|
|
## 1. BOARD-LEVEL PERSISTENCE (Persisted Across All Users)
|
|
|
|
### 1.1 Swimlanes
|
|
|
|
**Collection**: `swimlanes` ([models/swimlanes.js](models/swimlanes.js))
|
|
|
|
**Persisted Fields**:
|
|
- ✅ `title` - Swimlane title (via `rename()` mutation)
|
|
- ✅ `sort` - Swimlane ordering/position (decimal number)
|
|
- ✅ `color` - Swimlane color (via `setColor()` mutation)
|
|
- ✅ `collapsed` - Swimlane collapsed state (via `collapse()` mutation) **⚠️ See note below**
|
|
- ✅ `archived` - Swimlane archived status
|
|
|
|
**Persistence Mechanism**:
|
|
- Direct MongoDB updates via `Swimlanes.update()` and `Swimlanes.direct.update()`
|
|
- Automatic timestamps: `updatedAt`, `modifiedAt` fields
|
|
- Activity tracking for title changes and archive/restore operations
|
|
|
|
**Issues Found**:
|
|
- ⚠️ **ISSUE**: `collapsed` field in swimlanes.js line 127 is set to `defaultValue: false` but the `isCollapsed()` helper (line 251-263) checks for per-user stored values. This creates a mismatch between board-level and per-user storage.
|
|
|
|
---
|
|
|
|
### 1.2 Lists
|
|
|
|
**Collection**: `lists` ([models/lists.js](models/lists.js))
|
|
|
|
**Persisted Fields**:
|
|
- ✅ `title` - List title
|
|
- ✅ `sort` - List ordering/position (decimal number)
|
|
- ✅ `color` - List color
|
|
- ✅ `collapsed` - List collapsed state (board-wide via REST API line 768-775) **⚠️ See note below**
|
|
- ✅ `starred` - List starred status
|
|
- ✅ `wipLimit` - WIP limit configuration
|
|
- ✅ `archived` - List archived status
|
|
|
|
**Persistence Mechanism**:
|
|
- Direct MongoDB updates via `Lists.update()` and `Lists.direct.update()`
|
|
- Automatic timestamps: `updatedAt`, `modifiedAt`
|
|
- Activity tracking for title changes, archive/restore
|
|
|
|
**Issues Found**:
|
|
- ⚠️ **ISSUE**: Similar to swimlanes, `collapsed` field (line 147) defaults to `false` but the `isCollapsed()` helper (line 303-311) also checks for per-user stored values. The REST API allows board-level collapsed state updates (line 768-775), but client also stores per-user via `getCollapsedListFromStorage()`.
|
|
- ⚠️ **ISSUE**: The `swimlaneId` field is part of the list (line 48), but `draggableLists()` method (line 275) filters by board only, suggesting lists are shared across swimlanes rather than per-swimlane.
|
|
|
|
---
|
|
|
|
### 1.3 Cards
|
|
|
|
**Collection**: `cards` ([models/cards.js](models/cards.js))
|
|
|
|
**Persisted Fields**:
|
|
- ✅ `title` - Card title
|
|
- ✅ `sort` - Card ordering/position within list
|
|
- ✅ `color` - Card color (via `setColor()` mutation, line 2268)
|
|
- ✅ `boardId`, `swimlaneId`, `listId` - Card location
|
|
- ✅ `archived` - Card archived status
|
|
- ✅ `description` - Card description
|
|
- ✅ Custom fields, labels, members, assignees, etc.
|
|
|
|
**Persistence Mechanism**:
|
|
- `move()` method (line 2063+) handles reordering and moving cards across swimlanes/lists/boards
|
|
- Automatic timestamp updates via `modifiedAt`, `dateLastActivity`
|
|
- Activity tracking for moves, title changes, etc.
|
|
- Attachment metadata updated alongside card moves (line 2101-2115)
|
|
|
|
**Issues Found**:
|
|
- ✅ **OK**: Order/sort persistence working correctly via card.move() and card.moveOptionalArgs()
|
|
- ✅ **OK**: Color persistence working correctly
|
|
- ✅ **OK**: Title changes persisted automatically
|
|
|
|
---
|
|
|
|
### 1.4 Checklists
|
|
|
|
**Collection**: `checklists` ([models/checklists.js](models/checklists.js))
|
|
|
|
**Persisted Fields**:
|
|
- ✅ `title` - Checklist title (via `setTitle()` mutation)
|
|
- ✅ `sort` - Checklist ordering (decimal number)
|
|
- ✅ `hideCheckedChecklistItems` - Toggle for hiding checked items
|
|
- ✅ `hideAllChecklistItems` - Toggle for hiding all items
|
|
|
|
**Persistence Mechanism**:
|
|
- Direct MongoDB updates via `Checklists.update()`
|
|
- Automatic timestamps: `createdAt`, `modifiedAt`
|
|
- Activity tracking for creation and removal
|
|
|
|
---
|
|
|
|
### 1.5 Checklist Items
|
|
|
|
**Collection**: `checklistItems` ([models/checklistItems.js](models/checklistItems.js))
|
|
|
|
**Persisted Fields**:
|
|
- ✅ `title` - Item text (via `setTitle()` mutation)
|
|
- ✅ `sort` - Item ordering within checklist (decimal number)
|
|
- ✅ `isFinished` - Item completion status (via `check()`, `uncheck()`, `toggleItem()` mutations)
|
|
|
|
**Persistence Mechanism**:
|
|
- `move()` mutation (line 159-168) handles reordering within checklists
|
|
- Direct MongoDB updates via `ChecklistItems.update()`
|
|
- Automatic timestamps: `createdAt`, `modifiedAt`
|
|
- Activity tracking for item creation/removal and completion state changes
|
|
|
|
**Issue Found**:
|
|
- ✅ **OK**: Item order and completion status persist correctly
|
|
|
|
---
|
|
|
|
### 1.6 Position History Tracking
|
|
|
|
**Collection**: `positionHistory` ([models/positionHistory.js](models/positionHistory.js))
|
|
|
|
**Purpose**: Tracks original positions of swimlanes, lists, and cards before changes
|
|
|
|
**Features**:
|
|
- ✅ Stores original `sort` position
|
|
- ✅ Stores original titles
|
|
- ✅ Supports swimlanes, lists, and cards
|
|
- ✅ Provides helpers to check if entity moved from original position
|
|
|
|
**Implementation Notes**:
|
|
- Swimlanes track position automatically on insert (swimlanes.js line 387-393)
|
|
- Lists track position automatically on insert (lists.js line 487+)
|
|
- Can detect moves via `hasMoved()` and `hasMovedFromOriginalPosition()` helpers
|
|
|
|
---
|
|
|
|
## 2. PER-USER SETTINGS (NOT Persisted Across Boards)
|
|
|
|
### 2.1 Per-Board, Per-User Settings
|
|
|
|
**Storage**: User `profile` subdocuments ([models/users.js](models/users.js))
|
|
|
|
#### A. List Widths
|
|
- **Field**: `profile.listWidths` (line 527)
|
|
- **Structure**: `listWidths[boardId][listId] = width`
|
|
- **Persistence**: Via `setListWidth()` mutation (line 1834)
|
|
- **Retrieval**: `getListWidth()`, `getListWidthFromStorage()` (line 1288-1313)
|
|
- **Constraints**: Also stored in `profile.listConstraints`
|
|
- ✅ **Status**: Working correctly
|
|
|
|
#### B. Swimlane Heights
|
|
- **Field**: `profile.swimlaneHeights` (searchable in line 1047+)
|
|
- **Structure**: `swimlaneHeights[boardId][swimlaneId] = height`
|
|
- **Persistence**: Via `setSwimlaneHeight()` mutation (line 1878)
|
|
- **Retrieval**: `getSwimlaneHeight()`, `getSwimlaneHeightFromStorage()` (line 1050-1080)
|
|
- ✅ **Status**: Working correctly
|
|
|
|
#### C. Collapsed Swimlanes (Per-User)
|
|
- **Field**: `profile.collapsedSwimlanes` (line 1900)
|
|
- **Structure**: `collapsedSwimlanes[boardId][swimlaneId] = boolean`
|
|
- **Persistence**: Via `setCollapsedSwimlane()` mutation (line 1900-1906)
|
|
- **Retrieval**: `getCollapsedSwimlaneFromStorage()` (swimlanes.js line 251-263)
|
|
- **Client-Side Fallback**: `Users.getPublicCollapsedSwimlane()` for public/non-logged-in users (users.js line 60-73)
|
|
- ✅ **Status**: Working correctly for logged-in users
|
|
|
|
#### D. Collapsed Lists (Per-User)
|
|
- **Field**: `profile.collapsedLists` (line 1893)
|
|
- **Structure**: `collapsedLists[boardId][listId] = boolean`
|
|
- **Persistence**: Via `setCollapsedList()` mutation (line 1893-1899)
|
|
- **Retrieval**: `getCollapsedListFromStorage()` (lists.js line 303-311)
|
|
- **Client-Side Fallback**: `Users.getPublicCollapsedList()` for public users (users.js line 44-52)
|
|
- ✅ **Status**: Working correctly for logged-in users
|
|
|
|
#### E. Card Collapsed State (Global Per-User)
|
|
- **Field**: `profile.cardCollapsed` (line 267)
|
|
- **Persistence**: Via `setCardCollapsed()` method (line 2088-2091)
|
|
- **Retrieval**: `cardCollapsed()` helper in cardDetails.js (line 100-107)
|
|
- **Client-Side Fallback**: `Users.getPublicCardCollapsed()` for public users (users.js line 80-85)
|
|
- ✅ **Status**: Working correctly (applies to all boards for a user)
|
|
|
|
#### F. Card Maximized State (Global Per-User)
|
|
- **Field**: `profile.cardMaximized` (line 260)
|
|
- **Persistence**: Via `toggleCardMaximized()` mutation (line 1720-1726)
|
|
- **Retrieval**: `hasCardMaximized()` helper (line 1194-1196)
|
|
- ✅ **Status**: Working correctly
|
|
|
|
#### G. Board Workspace Trees (Global Per-User)
|
|
- **Field**: `profile.boardWorkspacesTree` (line 1981-2026)
|
|
- **Purpose**: Stores nested workspace structure for organizing boards
|
|
- **Persistence**: Via `setWorkspacesTree()` method (line 1995-2000)
|
|
- ✅ **Status**: Working correctly
|
|
|
|
#### H. Board Workspace Assignments (Global Per-User)
|
|
- **Field**: `profile.boardWorkspaceAssignments` (line 2002-2011)
|
|
- **Purpose**: Maps each board to a workspace ID
|
|
- **Persistence**: Via `assignBoardToWorkspace()` and `unassignBoardFromWorkspace()` methods
|
|
- ✅ **Status**: Working correctly
|
|
|
|
#### I. All Boards Workspaces Setting
|
|
- **Field**: `profile.boardView` (line 1807)
|
|
- **Persistence**: Via `setBoardView()` method (line 1805-1809)
|
|
- **Description**: Per-user preference for "All Boards" view style
|
|
- ✅ **Status**: Working correctly
|
|
|
|
---
|
|
|
|
### 2.2 Client-Side Storage (Non-Logged-In Users)
|
|
|
|
**Storage Methods**:
|
|
1. **Cookies** (via `readCookieMap()`/`writeCookieMap()`):
|
|
- `wekan-collapsed-lists` - Collapsed list states (users.js line 44-58)
|
|
- `wekan-collapsed-swimlanes` - Collapsed swimlane states
|
|
|
|
2. **localStorage**:
|
|
- `wekan-list-widths` - List widths (getListWidthFromStorage, line 1316-1327)
|
|
- `wekan-swimlane-heights` - Swimlane heights (setSwimlaneHeightToStorage, line 1100-1123)
|
|
|
|
**Coverage**:
|
|
- ✅ Collapse status for lists and swimlanes
|
|
- ✅ Width constraints for lists
|
|
- ✅ Height constraints for swimlanes
|
|
- ❌ Card collapsed state (only via cookies, fallback available)
|
|
|
|
---
|
|
|
|
## 3. CRITICAL FINDINGS & ISSUES
|
|
|
|
### 3.1 HIGH PRIORITY ISSUES
|
|
|
|
#### Issue #1: Collapsed State Inconsistency (Swimlanes)
|
|
**Severity**: HIGH
|
|
**Location**: [models/swimlanes.js](models/swimlanes.js) lines 127, 251-263
|
|
|
|
**Problem**:
|
|
- The swimlane schema defines `collapsed` as a board-level field (defaults to false)
|
|
- But the `isCollapsed()` helper prioritizes per-user stored values from the user profile
|
|
- This creates confusion: is collapsed state board-wide or per-user?
|
|
|
|
**Expected Behavior**: Per-user settings should be stored in `profile.collapsedSwimlanes`, not in the swimlane document itself.
|
|
|
|
**Recommendation**:
|
|
```javascript
|
|
// CURRENT (WRONG):
|
|
collapsed: {
|
|
type: Boolean,
|
|
defaultValue: false, // Board-wide field
|
|
},
|
|
|
|
// SUGGESTED (CORRECT):
|
|
// Remove 'collapsed' from swimlane schema
|
|
// Only store per-user state in profile.collapsedSwimlanes
|
|
```
|
|
|
|
---
|
|
|
|
#### Issue #2: Collapsed State Inconsistency (Lists)
|
|
**Severity**: HIGH
|
|
**Location**: [models/lists.js](models/lists.js) lines 147, 303-311
|
|
|
|
**Problem**:
|
|
- Similar to swimlanes, lists have a board-level `collapsed` field
|
|
- REST API allows updating this field (line 768-775)
|
|
- But `isCollapsed()` helper checks per-user values first
|
|
- Migrations copy `collapsed` status between lists (fixMissingListsMigration.js line 165)
|
|
|
|
**Recommendation**: Clarify whether collapsed state should be:
|
|
1. **Option A**: Board-level only (remove per-user override)
|
|
2. **Option B**: Per-user only (remove board-level field)
|
|
3. **Option C**: Hybrid with clear precedence rules
|
|
|
|
---
|
|
|
|
#### Issue #3: Swimlane/List Organization Model Unclear
|
|
**Severity**: MEDIUM
|
|
**Location**: [models/lists.js](models/lists.js) lines 48, 201-230, 275
|
|
|
|
**Problem**:
|
|
- Lists have a `swimlaneId` field but `draggableLists()` filters by `boardId` only
|
|
- Some methods reference `myLists()` which filters by both `boardId` and `swimlaneId`
|
|
- Migrations suggest lists were transitioning from per-swimlane to shared-across-swimlane model
|
|
|
|
**Questions**:
|
|
- Are lists shared across all swimlanes or isolated to each swimlane?
|
|
- What happens when dragging a list to a different swimlane?
|
|
|
|
**Recommendation**: Document the intended architecture clearly.
|
|
|
|
---
|
|
|
|
### 3.2 MEDIUM PRIORITY ISSUES
|
|
|
|
#### Issue #4: Position History Only Tracks Original Position
|
|
**Severity**: MEDIUM
|
|
**Location**: [models/positionHistory.js](models/positionHistory.js)
|
|
|
|
**Problem**:
|
|
- Position history tracks the *original* position when an entity is created
|
|
- It does NOT track subsequent moves/reorders
|
|
- Historical audit trail of all position changes is lost
|
|
|
|
**Impact**: Cannot determine full history of where a card/list was located over time
|
|
|
|
**Recommendation**: Consider extending to track all position changes with timestamps.
|
|
|
|
---
|
|
|
|
#### Issue #5: Card Collapsed State is Global Per-User, Not Per-Card
|
|
**Severity**: LOW
|
|
**Location**: [models/users.js](models/users.js) line 267, users.js line 2088-2091
|
|
|
|
**Problem**:
|
|
- `profile.cardCollapsed` is a single boolean affecting all cards for a user
|
|
- It's not per-card or per-board, just a global toggle
|
|
- Name is misleading
|
|
|
|
**Recommendation**: Consider renaming to `cardDetailsCollapsedByDefault` or similar.
|
|
|
|
---
|
|
|
|
#### Issue #6: Public User Settings Storage Incomplete
|
|
**Severity**: MEDIUM
|
|
**Location**: [models/users.js](models/users.js) lines 44-85
|
|
|
|
**Problem**:
|
|
- Cookie-based storage for public users only covers:
|
|
- Collapsed lists
|
|
- Collapsed swimlanes
|
|
- Missing storage for:
|
|
- List widths
|
|
- Swimlane heights
|
|
- Card collapsed state
|
|
|
|
**Impact**: Public/non-logged-in users lose UI preferences on page reload
|
|
|
|
**Recommendation**: Implement localStorage storage for all per-user preferences.
|
|
|
|
---
|
|
|
|
### 3.3 VERIFICATION CHECKLIST
|
|
|
|
| Item | Status | Notes |
|
|
|------|--------|-------|
|
|
| Swimlane order persistence | ✅ | Via `sort` field, board-level |
|
|
| List order persistence | ✅ | Via `sort` field, board-level |
|
|
| Card order persistence | ✅ | Via `sort` field, card.move() |
|
|
| Checklist order persistence | ✅ | Via `sort` field |
|
|
| Checklist item order persistence | ✅ | Via `sort` field, ChecklistItems.move() |
|
|
| Swimlane color changes | ✅ | Via `setColor()` mutation |
|
|
| List color changes | ✅ | Via REST API or direct update |
|
|
| Card color changes | ✅ | Via `setColor()` mutation |
|
|
| Swimlane title changes | ✅ | Via `rename()` mutation, activity tracked |
|
|
| List title changes | ✅ | Via REST API or `rename()` mutation, activity tracked |
|
|
| Card title changes | ✅ | Via direct update, activity tracked |
|
|
| Checklist title changes | ✅ | Via `setTitle()` mutation |
|
|
| Checklist item title changes | ✅ | Via `setTitle()` mutation |
|
|
| Per-user list widths | ✅ | Via `profile.listWidths` |
|
|
| Per-user swimlane heights | ✅ | Via `profile.swimlaneHeights` |
|
|
| Per-user swimlane collapse state | ✅ | Via `profile.collapsedSwimlanes` |
|
|
| Per-user list collapse state | ✅ | Via `profile.collapsedLists` |
|
|
| Per-user card collapse state | ✅ | Via `profile.cardCollapsed` |
|
|
| Per-user board workspace organization | ✅ | Via `profile.boardWorkspacesTree` |
|
|
| Activity logging for changes | ✅ | Via Activities collection |
|
|
|
|
---
|
|
|
|
## 4. RECOMMENDATIONS
|
|
|
|
### 4.1 Immediate Actions
|
|
|
|
1. **Clarify Collapsed State Architecture**
|
|
- Decide if collapsed state should be per-user or board-wide
|
|
- Update swimlanes.js and lists.js schema accordingly
|
|
- Update documentation
|
|
|
|
2. **Complete Public User Storage**
|
|
- Implement localStorage for list widths/swimlane heights for non-logged-in users
|
|
- Test persistence across page reloads
|
|
|
|
3. **Review Position History Usage**
|
|
- Confirm if current position history implementation meets requirements
|
|
- Consider extending to track all changes (not just original position)
|
|
|
|
### 4.2 Long-Term Improvements
|
|
|
|
1. **Audit Trail Feature**
|
|
- Extend position history to track all moves with timestamps
|
|
- Enable board managers to see complete history of card/list movements
|
|
|
|
2. **Data Integrity Tests**
|
|
- Add integration tests to verify:
|
|
- Order is persisted correctly after drag-drop
|
|
- Color changes persist across sessions
|
|
- Per-user settings apply only to correct user
|
|
- Per-user settings don't leak across boards
|
|
|
|
3. **Database Indexes**
|
|
- Verify indexes exist for common queries:
|
|
- `sort` fields for swimlanes, lists, cards, checklists
|
|
- `boardId` fields for filtering
|
|
|
|
### 4.3 Code Quality Improvements
|
|
|
|
1. **Document Persistence Model**
|
|
- Add clear comments explaining which fields are board-level vs. per-user
|
|
- Document swimlane/list relationship model
|
|
|
|
2. **Consistent Naming**
|
|
- Rename misleading field names (e.g., `cardCollapsed`)
|
|
- Align method names with actual functionality
|
|
|
|
---
|
|
|
|
## 5. SUMMARY TABLE
|
|
|
|
### Board-Level Persistence (Shared Across Users)
|
|
|
|
| Entity | Field | Type | Persisted | Notes |
|
|
|--------|-------|------|-----------|-------|
|
|
| Swimlane | title | Text | ✅ | Via rename() |
|
|
| Swimlane | sort | Number | ✅ | For ordering |
|
|
| Swimlane | color | String | ✅ | Via setColor() |
|
|
| Swimlane | collapsed | Boolean | ⚠️ | **Issue #1**: Conflicts with per-user storage |
|
|
| Swimlane | archived | Boolean | ✅ | Via archive()/restore() |
|
|
| | | | | |
|
|
| List | title | Text | ✅ | Via rename() or REST |
|
|
| List | sort | Number | ✅ | For ordering |
|
|
| List | color | String | ✅ | Via REST or update |
|
|
| List | collapsed | Boolean | ⚠️ | **Issue #2**: Conflicts with per-user storage |
|
|
| List | starred | Boolean | ✅ | Via REST or update |
|
|
| List | wipLimit | Object | ✅ | Via REST or setWipLimit() |
|
|
| List | archived | Boolean | ✅ | Via archive() |
|
|
| | | | | |
|
|
| Card | title | Text | ✅ | Direct update |
|
|
| Card | sort | Number | ✅ | Via move() |
|
|
| Card | color | String | ✅ | Via setColor() |
|
|
| Card | boardId/swimlaneId/listId | String | ✅ | Via move() |
|
|
| Card | archived | Boolean | ✅ | Via archive() |
|
|
| Card | description | Text | ✅ | Direct update |
|
|
| Card | customFields | Array | ✅ | Direct update |
|
|
| | | | | |
|
|
| Checklist | title | Text | ✅ | Via setTitle() |
|
|
| Checklist | sort | Number | ✅ | Direct update |
|
|
| Checklist | hideCheckedChecklistItems | Boolean | ✅ | Via toggle mutation |
|
|
| Checklist | hideAllChecklistItems | Boolean | ✅ | Via toggle mutation |
|
|
| | | | | |
|
|
| ChecklistItem | title | Text | ✅ | Via setTitle() |
|
|
| ChecklistItem | sort | Number | ✅ | Via move() |
|
|
| ChecklistItem | isFinished | Boolean | ✅ | Via check/uncheck/toggle |
|
|
|
|
### Per-User Settings (NOT Persisted Across Boards)
|
|
|
|
| Setting | Storage | Scope | Notes |
|
|
|---------|---------|-------|-------|
|
|
| List Widths | profile.listWidths | Per-board, per-user | ✅ Working |
|
|
| Swimlane Heights | profile.swimlaneHeights | Per-board, per-user | ✅ Working |
|
|
| Collapsed Swimlanes | profile.collapsedSwimlanes | Per-board, per-user | ✅ Working |
|
|
| Collapsed Lists | profile.collapsedLists | Per-board, per-user | ✅ Working |
|
|
| Card Collapsed State | profile.cardCollapsed | Global per-user | ⚠️ Name misleading |
|
|
| Card Maximized State | profile.cardMaximized | Global per-user | ✅ Working |
|
|
| Board Workspaces | profile.boardWorkspacesTree | Global per-user | ✅ Working |
|
|
| Board Workspace Assignments | profile.boardWorkspaceAssignments | Global per-user | ✅ Working |
|
|
| Board View Style | profile.boardView | Global per-user | ✅ Working |
|
|
|
|
---
|
|
|
|
## Document History
|
|
|
|
- **Created**: 2025-12-23
|
|
- **Status**: Initial Audit Complete
|
|
- **Reviewed**: Swimlanes, Lists, Cards, Checklists, ChecklistItems, PositionHistory, Users
|
|
- **Next Review**: After addressing high-priority issues
|
|
|