mirror of
https://github.com/wekan/wekan.git
synced 2026-02-27 10:24:07 +01:00
Per-User and Board-level data save fixes. Part 3.
Some checks are pending
Some checks are pending
Thanks to xet7 !
This commit is contained in:
parent
90a7a61904
commit
a039bb1066
12 changed files with 2996 additions and 82 deletions
323
docs/Security/PerUserDataAudit2025-12-23/CURRENT_STATUS.md
Normal file
323
docs/Security/PerUserDataAudit2025-12-23/CURRENT_STATUS.md
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
# Per-User Data Audit - Current Status Summary
|
||||
|
||||
**Last Updated**: 2025-12-23
|
||||
**Status**: ✅ Architecture Finalized
|
||||
**Scope**: All data persistence related to swimlanes, lists, cards, checklists, checklistItems
|
||||
|
||||
---
|
||||
|
||||
## Key Decision: Data Classification
|
||||
|
||||
The system now enforces clear separation:
|
||||
|
||||
### ✅ Per-Board Data (MongoDB Documents)
|
||||
Stored in swimlane/list/card/checklist/checklistItem documents. **All users see the same value.**
|
||||
|
||||
| Entity | Properties | Where Stored |
|
||||
|--------|-----------|-------------|
|
||||
| Swimlane | title, color, height, sort, archived | swimlanes.js document |
|
||||
| List | title, color, width, sort, archived, wipLimit, starred | lists.js document |
|
||||
| Card | title, color, description, swimlaneId, listId, sort, archived | cards.js document |
|
||||
| Checklist | title, sort, hideCheckedItems, hideAllItems | checklists.js document |
|
||||
| ChecklistItem | title, sort, isFinished | checklistItems.js document |
|
||||
|
||||
### 🔒 Per-User Data (User Profile + Cookies)
|
||||
Stored in user.profile or cookies. **Each user has their own value, not visible to others.**
|
||||
|
||||
| Entity | Properties | Where Stored |
|
||||
|--------|-----------|-------------|
|
||||
| User | collapsedSwimlanes | user.profile.collapsedSwimlanes[boardId][swimlaneId] |
|
||||
| User | collapsedLists | user.profile.collapsedLists[boardId][listId] |
|
||||
| User | hideMiniCardLabelText | user.profile.hideMiniCardLabelText[boardId] |
|
||||
| Public User | collapsedSwimlanes | Cookie: wekan-collapsed-swimlanes |
|
||||
| Public User | collapsedLists | Cookie: wekan-collapsed-lists |
|
||||
|
||||
---
|
||||
|
||||
## Changes Implemented ✅
|
||||
|
||||
### 1. Schema Changes (swimlanes.js, lists.js) ✅ DONE
|
||||
|
||||
**Swimlanes**: Added `height` field
|
||||
```javascript
|
||||
height: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
defaultValue: -1, // -1 = auto-height, 50-2000 = fixed
|
||||
custom() {
|
||||
const h = this.value;
|
||||
if (h !== -1 && (h < 50 || h > 2000)) {
|
||||
return 'heightOutOfRange';
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Lists**: Added `width` field
|
||||
```javascript
|
||||
width: {
|
||||
type: Number,
|
||||
optional: true,
|
||||
defaultValue: 272, // 100-1000 pixels
|
||||
custom() {
|
||||
const w = this.value;
|
||||
if (w < 100 || w > 1000) {
|
||||
return 'widthOutOfRange';
|
||||
}
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**Status**: ✅ Implemented in swimlanes.js and lists.js
|
||||
|
||||
### 2. Card Position Storage (cards.js) ✅ ALREADY CORRECT
|
||||
|
||||
Cards already store position per-board:
|
||||
- `sort` field: decimal number determining order (shared)
|
||||
- `swimlaneId`: which swimlane (shared)
|
||||
- `listId`: which list (shared)
|
||||
|
||||
**Status**: ✅ No changes needed
|
||||
|
||||
### 3. Checklist Position Storage (checklists.js) ✅ ALREADY CORRECT
|
||||
|
||||
Checklists already store position per-board:
|
||||
- `sort` field: decimal number determining order (shared)
|
||||
- `hideCheckedChecklistItems`: per-board setting
|
||||
- `hideAllChecklistItems`: per-board setting
|
||||
|
||||
**Status**: ✅ No changes needed
|
||||
|
||||
### 4. ChecklistItem Position Storage (checklistItems.js) ✅ ALREADY CORRECT
|
||||
|
||||
ChecklistItems already store position per-board:
|
||||
- `sort` field: decimal number determining order (shared)
|
||||
|
||||
**Status**: ✅ No changes needed
|
||||
|
||||
---
|
||||
|
||||
## Changes Not Yet Implemented
|
||||
|
||||
### 1. User Model Refactoring (users.js) ⏳ TODO
|
||||
|
||||
**Current State**: Users.js still has per-user width/height methods that read from user.profile:
|
||||
- `getListWidth(boardId, listId)` - reads user.profile.listWidths
|
||||
- `getSwimlaneHeight(boardId, swimlaneId)` - reads user.profile.swimlaneHeights
|
||||
- `setListWidth(boardId, listId, width)` - writes to user.profile.listWidths
|
||||
- `setSwimlaneHeight(boardId, swimlaneId, height)` - writes to user.profile.swimlaneHeights
|
||||
|
||||
**Required Change**:
|
||||
- Remove per-user width/height storage from user.profile
|
||||
- Refactor methods to read from list/swimlane documents instead
|
||||
- Remove from user schema definition
|
||||
|
||||
**Status**: ⏳ Pending - See IMPLEMENTATION_GUIDE.md for details
|
||||
|
||||
### 2. Migration Script ⏳ TODO
|
||||
|
||||
**Current State**: No migration exists to move existing per-user data to per-board
|
||||
|
||||
**Required**:
|
||||
- Create `server/migrations/migrateToPerBoardStorage.js`
|
||||
- Migrate user.profile.swimlaneHeights → swimlane.height
|
||||
- Migrate user.profile.listWidths → list.width
|
||||
- Remove old fields from user profiles
|
||||
- Track migration status
|
||||
|
||||
**Status**: ⏳ Pending - Template available in IMPLEMENTATION_GUIDE.md
|
||||
|
||||
---
|
||||
|
||||
## Data Examples
|
||||
|
||||
### Before (Mixed Per-User/Per-Board - WRONG)
|
||||
```javascript
|
||||
// Swimlane document (per-board)
|
||||
{
|
||||
_id: 'swim123',
|
||||
title: 'Development',
|
||||
boardId: 'board123',
|
||||
// height stored in user profile (per-user) - WRONG!
|
||||
}
|
||||
|
||||
// User A profile (per-user)
|
||||
{
|
||||
_id: 'userA',
|
||||
profile: {
|
||||
swimlaneHeights: {
|
||||
'board123': {
|
||||
'swim123': 300 // Only User A sees 300px height
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// User B profile (per-user)
|
||||
{
|
||||
_id: 'userB',
|
||||
profile: {
|
||||
swimlaneHeights: {
|
||||
'board123': {
|
||||
'swim123': 400 // Only User B sees 400px height
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### After (Correct Per-Board/Per-User Separation)
|
||||
```javascript
|
||||
// Swimlane document (per-board - ALL USERS SEE THIS)
|
||||
{
|
||||
_id: 'swim123',
|
||||
title: 'Development',
|
||||
boardId: 'board123',
|
||||
height: 300 // All users see 300px height
|
||||
}
|
||||
|
||||
// User A profile (per-user - only User A's preferences)
|
||||
{
|
||||
_id: 'userA',
|
||||
profile: {
|
||||
collapsedSwimlanes: {
|
||||
'board123': {
|
||||
'swim123': false // User A: swimlane not collapsed
|
||||
}
|
||||
},
|
||||
collapsedLists: { ... },
|
||||
hideMiniCardLabelText: { ... }
|
||||
// height and width REMOVED - now in documents
|
||||
}
|
||||
}
|
||||
|
||||
// User B profile (per-user - only User B's preferences)
|
||||
{
|
||||
_id: 'userB',
|
||||
profile: {
|
||||
collapsedSwimlanes: {
|
||||
'board123': {
|
||||
'swim123': true // User B: swimlane is collapsed
|
||||
}
|
||||
},
|
||||
collapsedLists: { ... },
|
||||
hideMiniCardLabelText: { ... }
|
||||
// height and width REMOVED - now in documents
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Evidence Required
|
||||
|
||||
### Before Starting UI Integration
|
||||
|
||||
1. **Schema Validation**
|
||||
- [ ] Swimlane with height = -1 → accepts
|
||||
- [ ] Swimlane with height = 100 → accepts
|
||||
- [ ] Swimlane with height = 25 → rejects (< 50)
|
||||
- [ ] Swimlane with height = 3000 → rejects (> 2000)
|
||||
|
||||
2. **Data Retrieval**
|
||||
- [ ] `Swimlanes.findOne('swim123').height` returns correct value
|
||||
- [ ] `Lists.findOne('list456').width` returns correct value
|
||||
- [ ] Default values used when not set
|
||||
|
||||
3. **Data Updates**
|
||||
- [ ] `Swimlanes.update('swim123', { $set: { height: 500 } })` succeeds
|
||||
- [ ] `Lists.update('list456', { $set: { width: 400 } })` succeeds
|
||||
|
||||
4. **Per-User Isolation**
|
||||
- [ ] User A collapses swimlane → User B's collapse status unchanged
|
||||
- [ ] User A hides labels → User B's visibility unchanged
|
||||
|
||||
---
|
||||
|
||||
## Integration Path
|
||||
|
||||
### Phase 1: ✅ Schema Definition (DONE)
|
||||
- Added `height` to Swimlanes
|
||||
- Added `width` to Lists
|
||||
- Both with validation (custom functions)
|
||||
|
||||
### Phase 2: ⏳ User Model Refactoring (NEXT)
|
||||
- Update user methods to read from documents
|
||||
- Remove per-user storage from user.profile
|
||||
- Create migration script
|
||||
|
||||
### Phase 3: ⏳ UI Integration (AFTER Phase 2)
|
||||
- Update client code to use new storage locations
|
||||
- Update Meteor methods to update documents
|
||||
- Update subscriptions if needed
|
||||
|
||||
### Phase 4: ⏳ Testing & Deployment (FINAL)
|
||||
- Run automated tests
|
||||
- Manual testing with multiple users
|
||||
- Deploy with data migration
|
||||
|
||||
---
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
### For Existing Installations
|
||||
- Old `user.profile.swimlaneHeights` data will be preserved until migration
|
||||
- Old `user.profile.listWidths` data will be preserved until migration
|
||||
- New code can read from either location during transition
|
||||
- Migration script handles moving data safely
|
||||
|
||||
### For New Installations
|
||||
- Only per-board storage will be used
|
||||
- User.profile will only contain per-user settings
|
||||
- No legacy data to migrate
|
||||
|
||||
---
|
||||
|
||||
## File Reference
|
||||
|
||||
| Document | Purpose |
|
||||
|----------|---------|
|
||||
| [DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md) | Complete architecture specification |
|
||||
| [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) | Step-by-step implementation instructions |
|
||||
| [models/swimlanes.js](../../../models/swimlanes.js) | Swimlane model with new height field |
|
||||
| [models/lists.js](../../../models/lists.js) | List model with new width field |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference: What Changed?
|
||||
|
||||
### New Behavior
|
||||
- **Swimlane Height**: Now stored in swimlane document (per-board)
|
||||
- **List Width**: Now stored in list document (per-board)
|
||||
- **Card Positions**: Always been in card document (per-board) ✅
|
||||
- **Collapse States**: Remain in user.profile (per-user) ✅
|
||||
- **Label Visibility**: Remains in user.profile (per-user) ✅
|
||||
|
||||
### Old Behavior (Being Removed)
|
||||
- ❌ Swimlane Height: Was in user.profile (per-user)
|
||||
- ❌ List Width: Was in user.profile (per-user)
|
||||
|
||||
### No Change (Already Correct)
|
||||
- ✅ Card Positions: In card document (per-board)
|
||||
- ✅ Checklist Positions: In checklist document (per-board)
|
||||
- ✅ Collapse States: In user.profile (per-user)
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
After all phases complete:
|
||||
|
||||
1. ✅ All swimlane heights stored in swimlane documents
|
||||
2. ✅ All list widths stored in list documents
|
||||
3. ✅ All positions stored in swimlane/list/card/checklist/checklistItem documents
|
||||
4. ✅ Only collapse states and label visibility in user profiles
|
||||
5. ✅ No duplicate storage of widths/heights
|
||||
6. ✅ All users see same dimensions for swimlanes/lists
|
||||
7. ✅ Each user has independent collapse preferences
|
||||
8. ✅ Data validates against range constraints
|
||||
|
||||
---
|
||||
|
||||
**Status**: ✅ Phase 1 Complete, Awaiting Phase 2
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue