Merge pull request #6139 from harryadel/feature/reactive-cache-async-migration

Migrate @wekanteam/meteor-reactive-cache
This commit is contained in:
Lauri Ojansivu 2026-02-19 21:58:37 +02:00 committed by GitHub
commit 1d910b8a89
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
121 changed files with 8748 additions and 2075 deletions

1
.meteorignore Normal file
View file

@ -0,0 +1 @@
npm-packages/

187
METEOR3_MIGRATION.md Normal file
View file

@ -0,0 +1,187 @@
# Meteor 3.0 Migration Guide
Reference document capturing patterns, constraints, and lessons learned during the async migration of WeKan from Meteor 2.16 toward Meteor 3.0 readiness.
---
## 1. Dual-Compatibility Strategy
WeKan runs on **Meteor 2.16 with Blaze 2.x**. The goal is dual compatibility: changes must work on 2.16 now and remain compatible with a future Meteor 3.0 upgrade.
**Key constraint:** Blaze 2.x does NOT support async template helpers. Client-side code must receive synchronous data.
---
## 2. ReactiveCache Facade Pattern
`ReactiveCache` dispatches to `ReactiveCacheServer` (async MongoDB) or `ReactiveCacheClient` (sync Minimongo).
**Rule:** Facade methods must NOT be `async`. They return a Promise on the server and data on the client. Server callers `await`; client code uses the return value directly.
```javascript
// CORRECT:
getBoard(boardId) {
if (Meteor.isServer) {
return ReactiveCacheServer.getBoard(boardId); // Returns Promise
} else {
return ReactiveCacheClient.getBoard(boardId); // Returns data
}
}
// WRONG:
async getBoard(boardId) { ... } // Wraps client return in Promise too!
```
---
## 3. Model Helpers (Collection.helpers)
Model helpers defined via `Collection.helpers({})` are used by Blaze templates. They must NOT be `async`.
```javascript
// CORRECT:
Cards.helpers({
board() {
return ReactiveCache.getBoard(this.boardId); // Promise on server, data on client
},
});
// WRONG:
Cards.helpers({
async board() { // Blaze gets Promise instead of data
return await ReactiveCache.getBoard(this.boardId);
},
});
```
**Server-side callers** of these helpers must `await` the result:
```javascript
// In a Meteor method or hook (server-only):
const board = await card.board();
```
---
## 4. Allow/Deny Callbacks Must Be Synchronous
Meteor 2.x evaluates allow/deny callbacks synchronously. An `async` callback returns a Promise:
- **allow** callback returning Promise (truthy) → always passes
- **deny** callback returning Promise (truthy) → always denies
**Rule:** Never use `async` in allow/deny. Replace `ReactiveCache` calls with direct sync Mongo calls.
```javascript
// CORRECT:
Cards.allow({
insert(userId, doc) {
return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId));
},
fetch: ['boardId'],
});
// WRONG:
Cards.allow({
async insert(userId, doc) {
return allowIsBoardMemberWithWriteAccess(userId, await ReactiveCache.getBoard(doc.boardId));
},
});
```
### Sync alternatives for common patterns:
| Async (broken in allow/deny) | Sync replacement |
|------------------------------|------------------|
| `await ReactiveCache.getBoard(id)` | `Boards.findOne(id)` |
| `await ReactiveCache.getCard(id)` | `Cards.findOne(id)` |
| `await ReactiveCache.getCurrentUser()` | `Meteor.users.findOne(userId)` |
| `await ReactiveCache.getBoards({...})` | `Boards.find({...}).fetch()` |
| `await card.board()` | `Boards.findOne(card.boardId)` |
**Note:** These sync Mongo calls (`findOne`, `find().fetch()`) are available in Meteor 2.x. In Meteor 3.0, they will be replaced by `findOneAsync` / `find().fetchAsync()`, which will require allow/deny callbacks to be reworked again (or replaced by Meteor 3.0's new permission model).
---
## 5. Server-Only Code CAN Be Async
Code that runs exclusively on the server can safely use `async`/`await`:
- `Meteor.methods({})` — method bodies
- `Meteor.publish()` — publication functions
- `JsonRoutes.add()` — REST API handlers
- `Collection.before.*` / `Collection.after.*` — collection hooks (via `matb33:collection-hooks`)
- Standalone server functions
```javascript
Meteor.methods({
async createCard(data) {
const board = await ReactiveCache.getBoard(data.boardId); // OK
// ...
},
});
```
---
## 6. forEach with await Anti-Pattern
`Array.forEach()` does not handle async callbacks — iterations run concurrently without awaiting.
```javascript
// WRONG:
items.forEach(async (item) => {
await processItem(item); // Runs all in parallel, not sequentially
});
// CORRECT:
for (const item of items) {
await processItem(item); // Runs sequentially
}
```
---
## 7. Client-Side Collection Updates
Meteor requires client-side collection updates to use `_id` as the selector:
```javascript
// CORRECT:
Lists.updateAsync(listId, { $set: { title: newTitle } });
// WRONG - fails with "Untrusted code may only update documents by ID":
Lists.updateAsync({ _id: listId, boardId: boardId }, { $set: { title: newTitle } });
```
---
## 8. Sync Meteor 2.x APIs to Convert for 3.0
These Meteor 2.x sync APIs will need conversion when upgrading to Meteor 3.0:
| Meteor 2.x (sync) | Meteor 3.0 (async) |
|--------------------|--------------------|
| `Collection.findOne()` | `Collection.findOneAsync()` |
| `Collection.find().fetch()` | `Collection.find().fetchAsync()` |
| `Collection.insert()` | `Collection.insertAsync()` |
| `Collection.update()` | `Collection.updateAsync()` |
| `Collection.remove()` | `Collection.removeAsync()` |
| `Collection.upsert()` | `Collection.upsertAsync()` |
| `Meteor.user()` | `Meteor.userAsync()` |
| `Meteor.userId()` | Remains sync |
**Current status:** Server-side code already uses async patterns via `ReactiveCache`. The sync `findOne()` calls in allow/deny callbacks will need to be addressed when Meteor 3.0's allow/deny system supports async (or is replaced).
---
## 9. Files Reference
Key files involved in the async migration:
| File | Role |
|------|------|
| `imports/reactiveCache.js` | ReactiveCache facade + Server/Client/Index implementations |
| `server/lib/utils.js` | Permission helper functions (`allowIsBoardMember*`) |
| `models/*.js` | Collection schemas, helpers, allow/deny, hooks, methods |
| `server/publications/*.js` | Meteor publications |
| `server/rulesHelper.js` | Rule trigger/action evaluation |
| `server/cronMigrationManager.js` | Cron-based migration jobs |

File diff suppressed because it is too large Load diff

View file

@ -47,7 +47,7 @@ AccessibilitySettings.attachSchema(
AccessibilitySettings.allow({
update(userId) {
const user = ReactiveCache.getUser(userId);
const user = Meteor.users.findOne(userId);
return user && user.isAdmin;
},
});

View file

@ -46,7 +46,7 @@ AccountSettings.attachSchema(
AccountSettings.allow({
update(userId) {
const user = ReactiveCache.getUser(userId);
const user = Meteor.users.findOne(userId);
return user && user.isAdmin;
},
});

View file

@ -5,13 +5,13 @@ Actions = new Mongo.Collection('actions');
Actions.allow({
insert(userId, doc) {
return allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
},
update(userId, doc) {
return allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
},
remove(userId, doc) {
return allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
},
});

View file

@ -105,12 +105,12 @@ if (Meteor.isServer) {
//Activities._collection.dropIndex({ labelId: 1 }, { partialFilterExpression: { labelId: { $exists: true } } });
});
Activities.after.insert((userId, doc) => {
Activities.after.insert(async (userId, doc) => {
const activity = Activities._transform(doc);
let participants = [];
let watchers = [];
let title = 'act-activity-notify';
const board = ReactiveCache.getBoard(activity.boardId);
const board = await ReactiveCache.getBoard(activity.boardId);
const description = `act-${activity.activityType}`;
const params = {
activityId: activity._id,
@ -118,7 +118,7 @@ if (Meteor.isServer) {
if (activity.userId) {
// No need send notification to user of activity
// participants = _.union(participants, [activity.userId]);
const user = activity.user();
const user = await activity.user();
if (user) {
if (user.getName()) {
params.user = user.getName();
@ -146,7 +146,7 @@ if (Meteor.isServer) {
params.boardId = activity.boardId;
}
if (activity.oldBoardId) {
const oldBoard = activity.oldBoard();
const oldBoard = await activity.oldBoard();
if (oldBoard) {
watchers = _.union(watchers, oldBoard.watchers || []);
params.oldBoard = oldBoard.title;
@ -155,10 +155,10 @@ if (Meteor.isServer) {
}
if (activity.memberId) {
participants = _.union(participants, [activity.memberId]);
params.member = activity.member().getName();
params.member = (await activity.member()).getName();
}
if (activity.listId) {
const list = activity.list();
const list = await activity.list();
if (list) {
if (list.watchers !== undefined) {
watchers = _.union(watchers, list.watchers || []);
@ -168,7 +168,7 @@ if (Meteor.isServer) {
}
}
if (activity.oldListId) {
const oldList = activity.oldList();
const oldList = await activity.oldList();
if (oldList) {
watchers = _.union(watchers, oldList.watchers || []);
params.oldList = oldList.title;
@ -176,7 +176,7 @@ if (Meteor.isServer) {
}
}
if (activity.oldSwimlaneId) {
const oldSwimlane = activity.oldSwimlane();
const oldSwimlane = await activity.oldSwimlane();
if (oldSwimlane) {
watchers = _.union(watchers, oldSwimlane.watchers || []);
params.oldSwimlane = oldSwimlane.title;
@ -184,7 +184,7 @@ if (Meteor.isServer) {
}
}
if (activity.cardId) {
const card = activity.card();
const card = await activity.card();
participants = _.union(participants, [card.userId], card.members || []);
watchers = _.union(watchers, card.watchers || []);
params.card = card.title;
@ -193,26 +193,26 @@ if (Meteor.isServer) {
params.cardId = activity.cardId;
}
if (activity.swimlaneId) {
const swimlane = activity.swimlane();
const swimlane = await activity.swimlane();
params.swimlane = swimlane.title;
params.swimlaneId = activity.swimlaneId;
}
if (activity.commentId) {
const comment = activity.comment();
const comment = await activity.comment();
params.comment = comment.text;
let hasMentions = false; // Track if comment has @mentions
if (board) {
const comment = params.comment;
const knownUsers = board.members
.filter((member) => member.isActive)
.map((member) => {
const u = ReactiveCache.getUser(member.userId);
if (u) {
member.username = u.username;
member.emails = u.emails;
}
return member;
});
// Build knownUsers with async user lookups
const knownUsers = [];
for (const member of board.members) {
const u = await ReactiveCache.getUser(member.userId);
if (u) {
member.username = u.username;
member.emails = u.emails;
}
knownUsers.push(member);
}
// Match @mentions including usernames with @ symbols (like email addresses)
// Pattern matches: @username, @user@example.com, @"quoted username"
const mentionRegex = /\B@(?:(?:"([\w.\s-]*)")|([\w.@-]+))/gi;
@ -227,55 +227,62 @@ if (Meteor.isServer) {
if (activity.boardId && username === 'board_members') {
// mentions all board members
const validUserIds = knownUsers
.map((u) => u.userId)
.filter((userId) => {
const user = ReactiveCache.getUser(userId);
return user && user._id;
});
const validUserIds = [];
for (const u of knownUsers) {
const user = await ReactiveCache.getUser(u.userId);
if (user && user._id) {
validUserIds.push(u.userId);
}
}
watchers = _.union(watchers, validUserIds);
title = 'act-atUserComment';
hasMentions = true;
} else if (activity.boardId && username === 'board_assignees') {
// mentions all assignees of all cards on the board
const allCards = ReactiveCache.getCards({ boardId: activity.boardId });
const allCards = await ReactiveCache.getCards({ boardId: activity.boardId });
const assigneeIds = [];
allCards.forEach((card) => {
for (const card of allCards) {
if (card.assignees && card.assignees.length > 0) {
card.assignees.forEach((assigneeId) => {
for (const assigneeId of card.assignees) {
// Only add if the user exists and is a board member
const user = ReactiveCache.getUser(assigneeId);
const user = await ReactiveCache.getUser(assigneeId);
if (user && _.findWhere(knownUsers, { userId: assigneeId })) {
assigneeIds.push(assigneeId);
}
});
}
}
});
}
watchers = _.union(watchers, assigneeIds);
title = 'act-atUserComment';
hasMentions = true;
} else if (activity.cardId && username === 'card_members') {
// mentions all card members if assigned
const card = activity.card();
const card = await activity.card();
if (card && card.members && card.members.length > 0) {
// Filter to only valid users who are board members
const validMembers = card.members.filter((memberId) => {
const user = ReactiveCache.getUser(memberId);
return user && user._id && _.findWhere(knownUsers, { userId: memberId });
});
const validMembers = [];
for (const memberId of card.members) {
const user = await ReactiveCache.getUser(memberId);
if (user && user._id && _.findWhere(knownUsers, { userId: memberId })) {
validMembers.push(memberId);
}
}
watchers = _.union(watchers, validMembers);
}
title = 'act-atUserComment';
hasMentions = true;
} else if (activity.cardId && username === 'card_assignees') {
// mentions all assignees of the current card
const card = activity.card();
const card = await activity.card();
if (card && card.assignees && card.assignees.length > 0) {
// Filter to only valid users who are board members
const validAssignees = card.assignees.filter((assigneeId) => {
const user = ReactiveCache.getUser(assigneeId);
return user && user._id && _.findWhere(knownUsers, { userId: assigneeId });
});
const validAssignees = [];
for (const assigneeId of card.assignees) {
const user = await ReactiveCache.getUser(assigneeId);
if (user && user._id && _.findWhere(knownUsers, { userId: assigneeId })) {
validAssignees.push(assigneeId);
}
}
watchers = _.union(watchers, validAssignees);
}
title = 'act-atUserComment';
@ -303,7 +310,7 @@ if (Meteor.isServer) {
params.attachmentId = activity.attachmentId;
}
if (activity.checklistId) {
const checklist = activity.checklist();
const checklist = await activity.checklist();
if (checklist) {
if (checklist.title) {
params.checklist = checklist.title;
@ -311,7 +318,7 @@ if (Meteor.isServer) {
}
}
if (activity.checklistItemId) {
const checklistItem = activity.checklistItem();
const checklistItem = await activity.checklistItem();
if (checklistItem) {
if (checklistItem.title) {
params.checklistItem = checklistItem.title;
@ -319,7 +326,7 @@ if (Meteor.isServer) {
}
}
if (activity.customFieldId) {
const customField = activity.customField();
const customField = await activity.customField();
if (customField) {
if (customField.name) {
params.customField = customField.name;
@ -331,7 +338,7 @@ if (Meteor.isServer) {
}
// Label activity did not work yet, unable to edit labels when tried this.
if (activity.labelId) {
const label = activity.label();
const label = await activity.label();
if (label) {
if (label.name) {
params.label = label.name;
@ -361,10 +368,8 @@ if (Meteor.isServer) {
try {
const atype = activity.activityType;
if (new RegExp(BIGEVENTS).exec(atype)) {
watchers = _.union(
watchers,
board.activeMembers().map((member) => member.userId),
); // notify all active members for important events
const activeMemberIds = _.filter(board.members, m => m.isActive === true).map(m => m.userId);
watchers = _.union(watchers, activeMemberIds); // notify all active members for important events
}
} catch (e) {
// passed env var BIGEVENTS_PATTERN is not a valid regex
@ -389,7 +394,7 @@ if (Meteor.isServer) {
);
}
}
Notifications.getUsers(watchers).forEach((user) => {
(await Notifications.getUsers(watchers)).forEach((user) => {
// Skip if user is undefined or doesn't have an _id (e.g., deleted user or invalid ID)
if (!user || !user._id) return;
@ -400,7 +405,7 @@ if (Meteor.isServer) {
}
});
const integrations = ReactiveCache.getIntegrations({
const integrations = await ReactiveCache.getIntegrations({
boardId: { $in: [board._id, Integrations.Const.GLOBAL_WEBHOOK_ID] },
// type: 'outgoing-webhooks', // all types
enabled: true,

View file

@ -51,7 +51,7 @@ Announcements.attachSchema(
Announcements.allow({
update(userId) {
const user = ReactiveCache.getUser(userId);
const user = Meteor.users.findOne(userId);
return user && user.isAdmin;
},
});

View file

@ -257,12 +257,12 @@ AttachmentStorageSettings.helpers({
if (Meteor.isServer) {
// Get or create default settings
Meteor.methods({
'getAttachmentStorageSettings'() {
async 'getAttachmentStorageSettings'() {
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(this.userId);
const user = await ReactiveCache.getUser(this.userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -306,13 +306,13 @@ if (Meteor.isServer) {
return settings;
},
'updateAttachmentStorageSettings'(settings) {
async 'updateAttachmentStorageSettings'(settings) {
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(this.userId);
const user = await ReactiveCache.getUser(this.userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -344,13 +344,13 @@ if (Meteor.isServer) {
const settings = AttachmentStorageSettings.findOne({});
return settings ? settings.getDefaultStorage() : STORAGE_NAME_FILESYSTEM;
},
'setDefaultAttachmentStorage'(storageName) {
async 'setDefaultAttachmentStorage'(storageName) {
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(this.userId);
const user = await ReactiveCache.getUser(this.userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -375,12 +375,12 @@ if (Meteor.isServer) {
});
// Publication for settings
Meteor.publish('attachmentStorageSettings', function() {
Meteor.publish('attachmentStorageSettings', async function() {
if (!this.userId) {
return this.ready();
}
const user = ReactiveCache.getUser(this.userId);
const user = await ReactiveCache.getUser(this.userId);
if (!user || !user.isAdmin) {
return this.ready();
}

View file

@ -179,13 +179,13 @@ Attachments = new FilesCollection({
// We authorize the attachment download either:
// - if the board is public, everyone (even unconnected) can download it
// - if the board is private, only board members can download it
protected(fileObj) {
async protected(fileObj) {
// file may have been deleted already again after upload validation failed
if (!fileObj) {
return false;
}
const board = ReactiveCache.getBoard(fileObj.meta.boardId);
const board = await ReactiveCache.getBoard(fileObj.meta.boardId);
if (board.isPublic()) {
return true;
}
@ -198,7 +198,7 @@ if (Meteor.isServer) {
Attachments.allow({
insert(userId, fileObj) {
// ReadOnly users cannot upload attachments
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(fileObj.boardId));
return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(fileObj.boardId));
},
update(userId, fileObj, fields) {
// SECURITY: The 'name' field is sanitized in onBeforeUpload and server-side methods,
@ -230,7 +230,7 @@ if (Meteor.isServer) {
}
// ReadOnly users cannot update attachments
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(fileObj.boardId));
return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(fileObj.boardId));
},
remove(userId, fileObj) {
// Additional security check: ensure the file belongs to the board the user has access to
@ -241,7 +241,7 @@ if (Meteor.isServer) {
return false;
}
const board = ReactiveCache.getBoard(fileObj.boardId);
const board = Boards.findOne(fileObj.boardId);
if (!board) {
if (process.env.DEBUG === 'true') {
console.warn('Blocked attachment removal: board not found');
@ -293,7 +293,7 @@ if (Meteor.isServer) {
return { valid: true };
},
moveAttachmentToStorage(fileObjId, storageDestination) {
async moveAttachmentToStorage(fileObjId, storageDestination) {
check(fileObjId, String);
check(storageDestination, String);
@ -301,12 +301,12 @@ if (Meteor.isServer) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const fileObj = ReactiveCache.getAttachment(fileObjId);
const fileObj = await ReactiveCache.getAttachment(fileObjId);
if (!fileObj) {
throw new Meteor.Error('attachment-not-found', 'Attachment not found');
}
const board = ReactiveCache.getBoard(fileObj.boardId);
const board = await ReactiveCache.getBoard(fileObj.boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
@ -319,7 +319,7 @@ if (Meteor.isServer) {
moveToStorage(fileObj, storageDestination, fileStoreStrategyFactory);
},
renameAttachment(fileObjId, newName) {
async renameAttachment(fileObjId, newName) {
check(fileObjId, String);
check(newName, String);
@ -328,13 +328,13 @@ if (Meteor.isServer) {
throw new Meteor.Error('not-authorized', 'User must be logged in');
}
const fileObj = ReactiveCache.getAttachment(fileObjId);
const fileObj = await ReactiveCache.getAttachment(fileObjId);
if (!fileObj) {
throw new Meteor.Error('file-not-found', 'Attachment not found');
}
// Verify the user has permission to modify this attachment
const board = ReactiveCache.getBoard(fileObj.boardId);
const board = await ReactiveCache.getBoard(fileObj.boardId);
if (!board) {
throw new Meteor.Error('board-not-found', 'Board not found');
}
@ -348,30 +348,30 @@ if (Meteor.isServer) {
rename(fileObj, newName, fileStoreStrategyFactory);
},
validateAttachment(fileObjId) {
async validateAttachment(fileObjId) {
check(fileObjId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const fileObj = ReactiveCache.getAttachment(fileObjId);
const fileObj = await ReactiveCache.getAttachment(fileObjId);
if (!fileObj) {
throw new Meteor.Error('attachment-not-found', 'Attachment not found');
}
const board = ReactiveCache.getBoard(fileObj.boardId);
const board = await ReactiveCache.getBoard(fileObj.boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
const isValid = Promise.await(isFileValid(fileObj, attachmentUploadMimeTypes, attachmentUploadSize, attachmentUploadExternalProgram));
const isValid = await isFileValid(fileObj, attachmentUploadMimeTypes, attachmentUploadSize, attachmentUploadExternalProgram);
if (!isValid) {
Attachments.remove(fileObjId);
}
},
validateAttachmentAndMoveToStorage(fileObjId, storageDestination) {
async validateAttachmentAndMoveToStorage(fileObjId, storageDestination) {
check(fileObjId, String);
check(storageDestination, String);
@ -379,12 +379,12 @@ if (Meteor.isServer) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const fileObj = ReactiveCache.getAttachment(fileObjId);
const fileObj = await ReactiveCache.getAttachment(fileObjId);
if (!fileObj) {
throw new Meteor.Error('attachment-not-found', 'Attachment not found');
}
const board = ReactiveCache.getBoard(fileObj.boardId);
const board = await ReactiveCache.getBoard(fileObj.boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
@ -395,9 +395,9 @@ if (Meteor.isServer) {
throw new Meteor.Error('invalid-storage-destination', 'Invalid storage destination');
}
Meteor.call('validateAttachment', fileObjId);
await Meteor.callAsync('validateAttachment', fileObjId);
const fileObjAfter = ReactiveCache.getAttachment(fileObjId);
const fileObjAfter = await ReactiveCache.getAttachment(fileObjId);
if (fileObjAfter) {
Meteor.defer(() => Meteor.call('moveAttachmentToStorage', fileObjId, storageDestination));

View file

@ -106,7 +106,7 @@ Avatars = new FilesCollection({
}
return TAPi18n.__('avatar-too-big', {size: filesize(avatarsUploadSize)});
},
onAfterUpload(fileObj) {
async onAfterUpload(fileObj) {
// current storage is the filesystem, update object and database
Object.keys(fileObj.versions).forEach(versionName => {
fileObj.versions[versionName].storage = STORAGE_NAME_FILESYSTEM;
@ -114,12 +114,13 @@ Avatars = new FilesCollection({
Avatars.update({ _id: fileObj._id }, { $set: { "versions": fileObj.versions } });
const isValid = Promise.await(isFileValid(fileObj, avatarsUploadMimeTypes, avatarsUploadSize, avatarsUploadExternalProgram));
const isValid = await isFileValid(fileObj, avatarsUploadMimeTypes, avatarsUploadSize, avatarsUploadExternalProgram);
if (isValid) {
// Set avatar URL using universal URL generator (URL-agnostic)
const universalUrl = generateUniversalAvatarUrl(fileObj._id);
ReactiveCache.getUser(fileObj.userId).setAvatarUrl(universalUrl);
const user = await ReactiveCache.getUser(fileObj.userId);
user.setAvatarUrl(universalUrl);
} else {
Avatars.remove(fileObj._id);
}
@ -128,12 +129,13 @@ Avatars = new FilesCollection({
const ret = fileStoreStrategyFactory.getFileStrategy(fileObj, versionName).interceptDownload(http, this.cacheControl);
return ret;
},
onBeforeRemove(files) {
files.forEach(fileObj => {
async onBeforeRemove(files) {
for (const fileObj of files) {
if (fileObj.userId) {
ReactiveCache.getUser(fileObj.userId).setAvatarUrl('');
const user = await ReactiveCache.getUser(fileObj.userId);
user.setAvatarUrl('');
}
});
}
return true;
},

View file

@ -703,12 +703,12 @@ Boards.attachSchema(
);
Boards.helpers({
copy() {
async copy() {
const oldId = this._id;
const oldWatchers = this.watchers ? this.watchers.slice() : [];
delete this._id;
delete this.slug;
this.title = this.copyTitle();
this.title = await this.copyTitle();
const _id = Boards.insert(this);
// Temporary remove watchers to disable notifications
@ -719,23 +719,26 @@ Boards.helpers({
});
// Copy all swimlanes in board
ReactiveCache.getSwimlanes({
const swimlanes = await ReactiveCache.getSwimlanes({
boardId: oldId,
archived: false,
}).forEach(swimlane => {
});
for (const swimlane of swimlanes) {
swimlane.type = 'swimlane';
swimlane.copy(_id);
});
}
// copy custom field definitions
const cfMap = {};
ReactiveCache.getCustomFields({ boardIds: oldId }).forEach(cf => {
const customFields = await ReactiveCache.getCustomFields({ boardIds: oldId });
for (const cf of customFields) {
const id = cf._id;
delete cf._id;
cf.boardIds = [_id];
cfMap[id] = CustomFields.insert(cf);
});
ReactiveCache.getCards({ boardId: _id }).forEach(card => {
}
const cards = await ReactiveCache.getCards({ boardId: _id });
for (const card of cards) {
Cards.update(card._id, {
$set: {
customFields: card.customFields.map(cf => {
@ -744,30 +747,33 @@ Boards.helpers({
}),
},
});
});
}
// copy rules, actions, and triggers
const actionsMap = {};
ReactiveCache.getActions({ boardId: oldId }).forEach(action => {
const actions = await ReactiveCache.getActions({ boardId: oldId });
for (const action of actions) {
const id = action._id;
delete action._id;
action.boardId = _id;
actionsMap[id] = Actions.insert(action);
});
}
const triggersMap = {};
ReactiveCache.getTriggers({ boardId: oldId }).forEach(trigger => {
const triggers = await ReactiveCache.getTriggers({ boardId: oldId });
for (const trigger of triggers) {
const id = trigger._id;
delete trigger._id;
trigger.boardId = _id;
triggersMap[id] = Triggers.insert(trigger);
});
ReactiveCache.getRules({ boardId: oldId }).forEach(rule => {
}
const rules = await ReactiveCache.getRules({ boardId: oldId });
for (const rule of rules) {
delete rule._id;
rule.boardId = _id;
rule.actionId = actionsMap[rule.actionId];
rule.triggerId = triggersMap[rule.triggerId];
Rules.insert(rule);
});
}
// Re-set Watchers to reenable notification
Boards.update(_id, {
@ -781,8 +787,8 @@ Boards.helpers({
*
* @returns {string|null}
*/
copyTitle() {
return Boards.uniqueTitle(this.title);
async copyTitle() {
return await Boards.uniqueTitle(this.title);
},
/**
@ -837,7 +843,8 @@ Boards.helpers({
newestLists() {
// sorted lists from newest to the oldest, by its creation date or its cards' last modification date
const value = ReactiveCache.getCurrentUser()._getListSortBy();
const user = ReactiveCache.getCurrentUser();
const value = user._getListSortBy();
const sortKey = { starred: -1, [value[0]]: value[1] }; // [["starred",-1],value];
return ReactiveCache.getLists(
{
@ -924,9 +931,9 @@ Boards.helpers({
let linkedBoardId = [this._id];
ReactiveCache.getCards({
"type": "cardType-linkedBoard",
"boardId": this._id}
).forEach(card => {
linkedBoardId.push(card.linkedId);
"boardId": this._id
}).forEach(card => {
linkedBoardId.push(card.linkedId);
});
const ret = ReactiveCache.getActivities({ boardId: { $in: linkedBoardId } }, { sort: { createdAt: -1 } });
return ret;
@ -1455,7 +1462,7 @@ Boards.helpers({
},
async setBackgroundImageURL(backgroundImageURL) {
const currentUser = ReactiveCache.getCurrentUser();
const currentUser = await ReactiveCache.getCurrentUser();
if (currentUser.isBoardAdmin() || currentUser.isAdmin()) {
return await Boards.updateAsync(this._id, { $set: { backgroundImageURL } });
}
@ -1718,31 +1725,32 @@ function boardRemover(userId, doc) {
);
}
Boards.uniqueTitle = title => {
Boards.uniqueTitle = async title => {
const m = title.match(
new RegExp('^(?<title>.*?)\\s*(\\[(?<num>\\d+)]\\s*$|\\s*$)'),
);
const base = escapeForRegex(m.groups.title);
const baseTitle = m.groups.title;
boards = ReactiveCache.getBoards({ title: new RegExp(`^${base}\\s*(\\[(?<num>\\d+)]\\s*$|\\s*$)`) });
const boards = await ReactiveCache.getBoards({ title: new RegExp(`^${base}\\s*(\\[(?<num>\\d+)]\\s*$|\\s*$)`) });
if (boards.length > 0) {
let num = 0;
ReactiveCache.getBoards({ title: new RegExp(`^${base}\\s*\\[\\d+]\\s*$`) }).forEach(
board => {
const m = board.title.match(
new RegExp('^(?<title>.*?)\\s*\\[(?<num>\\d+)]\\s*$'),
);
if (m) {
const n = parseInt(m.groups.num, 10);
num = num < n ? n : num;
}
},
);
const numberedBoards = await ReactiveCache.getBoards({ title: new RegExp(`^${base}\\s*\\[\\d+]\\s*$`) });
for (const board of numberedBoards) {
const m = board.title.match(
new RegExp('^(?<title>.*?)\\s*\\[(?<num>\\d+)]\\s*$'),
);
if (m) {
const n = parseInt(m.groups.num, 10);
num = num < n ? n : num;
}
}
return `${baseTitle} [${num + 1}]`;
}
return title;
};
// Non-async: returns data on client, Promise on server.
// Server callers must await.
Boards.userSearch = (
userId,
selector = {},
@ -1757,42 +1765,51 @@ Boards.userSearch = (
if (userId) {
selector.$or.push({ members: { $elemMatch: { userId, isActive: true } } });
}
const ret = ReactiveCache.getBoards(selector, projection);
return ret;
return ReactiveCache.getBoards(selector, projection);
};
// Non-async: returns data on client (for Blaze templates), Promise on server.
// Server callers must await.
Boards.userBoards = (
userId,
archived = false,
selector = {},
projection = {},
) => {
const _buildSelector = (user) => {
if (!user) return null;
if (typeof archived === 'boolean') {
selector.archived = archived;
}
if (!selector.type) {
selector.type = 'board';
}
selector.$or = [
{ permission: 'public' },
{ members: { $elemMatch: { userId, isActive: true } } },
{ orgs: { $elemMatch: { orgId: { $in: user.orgIds() }, isActive: true } } },
{ teams: { $elemMatch: { teamId: { $in: user.teamIds() }, isActive: true } } },
];
return selector;
};
if (Meteor.isServer) {
return (async () => {
const user = await ReactiveCache.getUser(userId);
if (!_buildSelector(user)) return [];
return await ReactiveCache.getBoards(selector, projection);
})();
}
const user = ReactiveCache.getUser(userId);
if (!user) {
return [];
}
if (typeof archived === 'boolean') {
selector.archived = archived;
}
if (!selector.type) {
selector.type = 'board';
}
selector.$or = [
{ permission: 'public' },
{ members: { $elemMatch: { userId, isActive: true } } },
{ orgs: { $elemMatch: { orgId: { $in: user.orgIds() }, isActive: true } } },
{ teams: { $elemMatch: { teamId: { $in: user.teamIds() }, isActive: true } } },
];
if (!_buildSelector(user)) return [];
return ReactiveCache.getBoards(selector, projection);
};
Boards.userBoardIds = (userId, archived = false, selector = {}) => {
return Boards.userBoards(userId, archived, selector, {
Boards.userBoardIds = async (userId, archived = false, selector = {}) => {
const boards = await Boards.userBoards(userId, archived, selector, {
fields: { _id: 1 },
}).map(board => {
});
return boards.map(board => {
return board._id;
});
};
@ -1888,13 +1905,13 @@ if (Meteor.isServer) {
});
Meteor.methods({
getBackgroundImageURL(boardId) {
async getBackgroundImageURL(boardId) {
check(boardId, String);
return ReactiveCache.getBoard(boardId, {}, { backgroundImageUrl: 1 });
return await ReactiveCache.getBoard(boardId, {}, { backgroundImageUrl: 1 });
},
async quitBoard(boardId) {
check(boardId, String);
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (board) {
const userId = Meteor.userId();
const index = board.memberIndex(userId);
@ -1904,9 +1921,9 @@ if (Meteor.isServer) {
} else throw new Meteor.Error('error-board-notAMember');
} else throw new Meteor.Error('error-board-doesNotExist');
},
acceptInvite(boardId) {
async acceptInvite(boardId) {
check(boardId, String);
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
throw new Meteor.Error('error-board-doesNotExist');
}
@ -1928,9 +1945,10 @@ if (Meteor.isServer) {
}
});
},
myLabelNames() {
async myLabelNames() {
let names = [];
Boards.userBoards(Meteor.userId()).forEach(board => {
const boards = await Boards.userBoards(Meteor.userId());
for (const board of boards) {
// Only return labels when they exist.
if (board.labels !== undefined) {
names = names.concat(
@ -1940,21 +1958,21 @@ if (Meteor.isServer) {
return label.name;
}),
);
} else {
return [];
}
});
}
return _.uniq(names).sort();
},
myBoardNames() {
async myBoardNames() {
const boards = await Boards.userBoards(Meteor.userId());
return _.uniq(
Boards.userBoards(Meteor.userId()).map(board => {
boards.map(board => {
return board.title;
}),
).sort();
},
setAllBoardsHideActivities() {
if ((ReactiveCache.getCurrentUser() || {}).isAdmin) {
async setAllBoardsHideActivities() {
const currentUser = await ReactiveCache.getCurrentUser();
if ((currentUser || {}).isAdmin) {
Boards.update(
{
showActivities: true
@ -1978,7 +1996,7 @@ if (Meteor.isServer) {
Meteor.methods({
async archiveBoard(boardId) {
check(boardId, String);
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (board) {
const userId = Meteor.userId();
const index = board.memberIndex(userId);
@ -1988,14 +2006,14 @@ if (Meteor.isServer) {
} else throw new Meteor.Error('error-board-notAMember');
} else throw new Meteor.Error('error-board-doesNotExist');
},
setBoardOrgs(boardOrgsArray, currBoardId){
async setBoardOrgs(boardOrgsArray, currBoardId){
check(boardOrgsArray, Array);
check(currBoardId, String);
const userId = Meteor.userId();
if (!userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in to perform this action.');
}
const board = ReactiveCache.getBoard(currBoardId);
const board = await ReactiveCache.getBoard(currBoardId);
if (!board) {
throw new Meteor.Error('board-not-found', 'Board not found.');
}
@ -2014,7 +2032,7 @@ if (Meteor.isServer) {
},
});
},
setBoardTeams(boardTeamsArray, membersArray, currBoardId){
async setBoardTeams(boardTeamsArray, membersArray, currBoardId){
check(boardTeamsArray, Array);
check(membersArray, Array);
check(currBoardId, String);
@ -2022,7 +2040,7 @@ if (Meteor.isServer) {
if (!userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in to perform this action.');
}
const board = ReactiveCache.getBoard(currBoardId);
const board = await ReactiveCache.getBoard(currBoardId);
if (!board) {
throw new Meteor.Error('board-not-found', 'Board not found.');
}
@ -2059,8 +2077,8 @@ if (Meteor.isServer) {
}
// Insert new board at last position in sort order.
Boards.before.insert((userId, doc) => {
const lastBoard = ReactiveCache.getBoard(
Boards.before.insert(async (userId, doc) => {
const lastBoard = await ReactiveCache.getBoard(
{ sort: { $exists: true } },
{ sort: { sort: -1 } },
);
@ -2244,7 +2262,7 @@ if (Meteor.isServer) {
* @return_type [{_id: string,
* title: string}]
*/
JsonRoutes.add('GET', '/api/users/:userId/boards', function(req, res) {
JsonRoutes.add('GET', '/api/users/:userId/boards', async function(req, res) {
try {
Authentication.checkLoggedIn(req.userId);
const paramUserId = req.params.userId;
@ -2255,7 +2273,7 @@ if (Meteor.isServer) {
req.userId === paramUserId,
);
const data = ReactiveCache.getBoards(
const boards = await ReactiveCache.getBoards(
{
archived: false,
'members.userId': paramUserId,
@ -2263,7 +2281,8 @@ if (Meteor.isServer) {
{
sort: { sort: 1 /* boards default sorting */ },
},
).map(function(board) {
);
const data = boards.map(function(board) {
return {
_id: board._id,
title: board.title,
@ -2286,17 +2305,18 @@ if (Meteor.isServer) {
* @return_type [{_id: string,
title: string}]
*/
JsonRoutes.add('GET', '/api/boards', function(req, res) {
JsonRoutes.add('GET', '/api/boards', async function(req, res) {
try {
Authentication.checkUserId(req.userId);
const boards = await ReactiveCache.getBoards(
{ permission: 'public' },
{
sort: { sort: 1 /* boards default sorting */ },
},
);
JsonRoutes.sendResult(res, {
code: 200,
data: ReactiveCache.getBoards(
{ permission: 'public' },
{
sort: { sort: 1 /* boards default sorting */ },
},
).map(function(doc) {
data: boards.map(function(doc) {
return {
_id: doc._id,
title: doc.title,
@ -2317,14 +2337,16 @@ if (Meteor.isServer) {
*
* @return_type {private: integer, public: integer}
*/
JsonRoutes.add('GET', '/api/boards_count', function(req, res) {
JsonRoutes.add('GET', '/api/boards_count', async function(req, res) {
try {
Authentication.checkUserId(req.userId);
const privateBoards = await ReactiveCache.getBoards({ permission: 'private' });
const publicBoards = await ReactiveCache.getBoards({ permission: 'public' });
JsonRoutes.sendResult(res, {
code: 200,
data: {
private: ReactiveCache.getBoards({ permission: 'private' }).length,
public: ReactiveCache.getBoards({ permission: 'public' }).length,
private: privateBoards.length,
public: publicBoards.length,
},
});
} catch (error) {
@ -2342,14 +2364,15 @@ if (Meteor.isServer) {
* @param {string} boardId the ID of the board to retrieve the data
* @return_type Boards
*/
JsonRoutes.add('GET', '/api/boards/:boardId', function(req, res) {
JsonRoutes.add('GET', '/api/boards/:boardId', async function(req, res) {
try {
const paramBoardId = req.params.boardId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
const board = await ReactiveCache.getBoard(paramBoardId);
JsonRoutes.sendResult(res, {
code: 200,
data: ReactiveCache.getBoard(paramBoardId),
data: board,
});
} catch (error) {
JsonRoutes.sendResult(res, {
@ -2494,12 +2517,12 @@ if (Meteor.isServer) {
*
* @return_type string
*/
JsonRoutes.add('PUT', '/api/boards/:boardId/labels', function(req, res) {
JsonRoutes.add('PUT', '/api/boards/:boardId/labels', async function(req, res) {
const id = req.params.boardId;
Authentication.checkBoardWriteAccess(req.userId, id);
try {
if (req.body.hasOwnProperty('label')) {
const board = ReactiveCache.getBoard(id);
const board = await ReactiveCache.getBoard(id);
const color = req.body.label.color;
const name = req.body.label.name;
const labelId = Random.id(6);
@ -2537,14 +2560,14 @@ if (Meteor.isServer) {
*
* @return_type string
*/
JsonRoutes.add('POST', '/api/boards/:boardId/copy', function(req, res) {
JsonRoutes.add('POST', '/api/boards/:boardId/copy', async function(req, res) {
const id = req.params.boardId;
const board = ReactiveCache.getBoard(id);
const board = await ReactiveCache.getBoard(id);
const adminAccess = board.members.some(e => e.userId === req.userId && e.isAdmin);
Authentication.checkAdminOrCondition(req.userId, adminAccess);
try {
board['title'] = req.body.title || Boards.uniqueTitle(board.title);
ret = board.copy();
board['title'] = req.body.title || await Boards.uniqueTitle(board.title);
ret = await board.copy();
JsonRoutes.sendResult(res, {
code: 200,
data: ret,
@ -2581,7 +2604,7 @@ JsonRoutes.add('POST', '/api/boards/:boardId/copy', function(req, res) {
const boardId = req.params.boardId;
const memberId = req.params.memberId;
const { isAdmin, isNoComments, isCommentOnly, isWorker, isNormalAssignedOnly, isCommentAssignedOnly, isReadOnly, isReadAssignedOnly } = req.body;
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
function isTrue(data) {
try {
return data.toLowerCase() === 'true';
@ -2631,13 +2654,14 @@ JsonRoutes.add('POST', '/api/boards/:boardId/copy', function(req, res) {
* cardId: string
* }]
*/
JsonRoutes.add('GET', '/api/boards/:boardId/attachments', function(req, res) {
JsonRoutes.add('GET', '/api/boards/:boardId/attachments', async function(req, res) {
const paramBoardId = req.params.boardId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
const attachments = await ReactiveCache
.getAttachments({'meta.boardId': paramBoardId }, {}, true);
JsonRoutes.sendResult(res, {
code: 200,
data: ReactiveCache
.getAttachments({'meta.boardId': paramBoardId }, {}, true)
data: attachments
.each()
.map(function(attachment) {
return {

View file

@ -51,13 +51,13 @@ CardCommentReactions.attachSchema(
CardCommentReactions.allow({
insert(userId, doc) {
return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
},
update(userId, doc) {
return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
},
remove(userId, doc) {
return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
},
fetch: ['boardId'],
});

View file

@ -83,13 +83,13 @@ CardComments.attachSchema(
CardComments.allow({
insert(userId, doc) {
// ReadOnly users cannot add comments. Only members who can comment are allowed.
return allowIsBoardMemberCommentOnly(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardMemberCommentOnly(userId, Boards.findOne(doc.boardId));
},
update(userId, doc) {
return userId === doc.userId || allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId));
return userId === doc.userId || allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
},
remove(userId, doc) {
return userId === doc.userId || allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId));
return userId === doc.userId || allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
},
fetch: ['userId', 'boardId'],
});
@ -154,8 +154,8 @@ CardComments.helpers({
CardComments.hookOptions.after.update = { fetchPrevious: false };
function commentCreation(userId, doc) {
const card = ReactiveCache.getCard(doc.cardId);
async function commentCreation(userId, doc) {
const card = await ReactiveCache.getCard(doc.cardId);
Activities.insert({
userId,
activityType: 'addComment',
@ -167,9 +167,9 @@ function commentCreation(userId, doc) {
});
}
CardComments.textSearch = (userId, textArray) => {
CardComments.textSearch = async (userId, textArray) => {
const selector = {
boardId: { $in: Boards.userBoardIds(userId) },
boardId: { $in: await Boards.userBoardIds(userId) },
$and: [],
};
@ -180,7 +180,7 @@ CardComments.textSearch = (userId, textArray) => {
// eslint-disable-next-line no-console
// console.log('cardComments selector:', selector);
const comments = ReactiveCache.getCardComments(selector);
const comments = await ReactiveCache.getCardComments(selector);
// eslint-disable-next-line no-console
// console.log('count:', comments.count());
// eslint-disable-next-line no-console
@ -197,12 +197,12 @@ if (Meteor.isServer) {
await CardComments._collection.createIndexAsync({ cardId: 1, createdAt: -1 });
});
CardComments.after.insert((userId, doc) => {
commentCreation(userId, doc);
CardComments.after.insert(async (userId, doc) => {
await commentCreation(userId, doc);
});
CardComments.after.update((userId, doc) => {
const card = ReactiveCache.getCard(doc.cardId);
CardComments.after.update(async (userId, doc) => {
const card = await ReactiveCache.getCard(doc.cardId);
Activities.insert({
userId,
activityType: 'editComment',
@ -214,8 +214,8 @@ if (Meteor.isServer) {
});
});
CardComments.before.remove((userId, doc) => {
const card = ReactiveCache.getCard(doc.cardId);
CardComments.before.remove(async (userId, doc) => {
const card = await ReactiveCache.getCard(doc.cardId);
Activities.insert({
userId,
activityType: 'deleteComment',
@ -225,7 +225,7 @@ if (Meteor.isServer) {
listId: card.listId,
swimlaneId: card.swimlaneId,
});
const activity = ReactiveCache.getActivity({ commentId: doc._id });
const activity = await ReactiveCache.getActivity({ commentId: doc._id });
if (activity) {
Activities.remove(activity._id);
}
@ -244,7 +244,7 @@ if (Meteor.isServer) {
* comment: string,
* authorId: string}]
*/
JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/comments', function (
JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/comments', async function (
req,
res,
) {
@ -254,10 +254,10 @@ if (Meteor.isServer) {
Authentication.checkBoardAccess(req.userId, paramBoardId);
JsonRoutes.sendResult(res, {
code: 200,
data: ReactiveCache.getCardComments({
data: (await ReactiveCache.getCardComments({
boardId: paramBoardId,
cardId: paramCardId,
}).map(function (doc) {
})).map(function (doc) {
return {
_id: doc._id,
comment: doc.text,
@ -285,7 +285,7 @@ if (Meteor.isServer) {
JsonRoutes.add(
'GET',
'/api/boards/:boardId/cards/:cardId/comments/:commentId',
function (req, res) {
async function (req, res) {
try {
const paramBoardId = req.params.boardId;
const paramCommentId = req.params.commentId;
@ -293,7 +293,7 @@ if (Meteor.isServer) {
Authentication.checkBoardAccess(req.userId, paramBoardId);
JsonRoutes.sendResult(res, {
code: 200,
data: ReactiveCache.getCardComment({
data: await ReactiveCache.getCardComment({
_id: paramCommentId,
cardId: paramCardId,
boardId: paramBoardId,
@ -320,7 +320,7 @@ if (Meteor.isServer) {
JsonRoutes.add(
'POST',
'/api/boards/:boardId/cards/:cardId/comments',
function (req, res) {
async function (req, res) {
try {
const paramBoardId = req.params.boardId;
const paramCardId = req.params.cardId;
@ -339,12 +339,12 @@ if (Meteor.isServer) {
},
});
const cardComment = ReactiveCache.getCardComment({
const cardComment = await ReactiveCache.getCardComment({
_id: id,
cardId: paramCardId,
boardId: paramBoardId,
});
commentCreation(req.userId, cardComment);
await commentCreation(req.userId, cardComment);
} catch (error) {
JsonRoutes.sendResult(res, {
code: 200,

File diff suppressed because it is too large Load diff

View file

@ -71,15 +71,15 @@ ChecklistItems.attachSchema(
ChecklistItems.allow({
insert(userId, doc) {
// ReadOnly users cannot create checklist items
return allowIsBoardMemberWithWriteAccessByCard(userId, ReactiveCache.getCard(doc.cardId));
return allowIsBoardMemberWithWriteAccessByCard(userId, Cards.findOne(doc.cardId));
},
update(userId, doc) {
// ReadOnly users cannot edit checklist items
return allowIsBoardMemberWithWriteAccessByCard(userId, ReactiveCache.getCard(doc.cardId));
return allowIsBoardMemberWithWriteAccessByCard(userId, Cards.findOne(doc.cardId));
},
remove(userId, doc) {
// ReadOnly users cannot delete checklist items
return allowIsBoardMemberWithWriteAccessByCard(userId, ReactiveCache.getCard(doc.cardId));
return allowIsBoardMemberWithWriteAccessByCard(userId, Cards.findOne(doc.cardId));
},
fetch: ['userId', 'cardId'],
});
@ -104,7 +104,8 @@ ChecklistItems.helpers({
return await ChecklistItems.updateAsync(this._id, { $set: { isFinished: !this.isFinished } });
},
async move(checklistId, sortIndex) {
const cardId = ReactiveCache.getChecklist(checklistId).cardId;
const checklist = await ReactiveCache.getChecklist(checklistId);
const cardId = checklist.cardId;
return await ChecklistItems.updateAsync(this._id, {
$set: { cardId, checklistId, sort: sortIndex },
});
@ -112,8 +113,8 @@ ChecklistItems.helpers({
});
// Activities helper
function itemCreation(userId, doc) {
const card = ReactiveCache.getCard(doc.cardId);
async function itemCreation(userId, doc) {
const card = await ReactiveCache.getCard(doc.cardId);
const boardId = card.boardId;
Activities.insert({
userId,
@ -134,8 +135,8 @@ function itemRemover(userId, doc) {
});
}
function publishCheckActivity(userId, doc) {
const card = ReactiveCache.getCard(doc.cardId);
async function publishCheckActivity(userId, doc) {
const card = await ReactiveCache.getCard(doc.cardId);
const boardId = card.boardId;
let activityType;
if (doc.isFinished) {
@ -157,12 +158,15 @@ function publishCheckActivity(userId, doc) {
Activities.insert(act);
}
function publishChekListCompleted(userId, doc) {
const card = ReactiveCache.getCard(doc.cardId);
async function publishChekListCompleted(userId, doc) {
const card = await ReactiveCache.getCard(doc.cardId);
const boardId = card.boardId;
const checklistId = doc.checklistId;
const checkList = ReactiveCache.getChecklist(checklistId);
if (checkList.isFinished()) {
const checkList = await ReactiveCache.getChecklist(checklistId);
const checklistItems = await ReactiveCache.getChecklistItems({ checklistId });
const isChecklistFinished = checkList.hideAllChecklistItems ||
(checklistItems.length > 0 && checklistItems.length === checklistItems.filter(i => i.isFinished).length);
if (isChecklistFinished) {
const act = {
userId,
activityType: 'completeChecklist',
@ -177,16 +181,16 @@ function publishChekListCompleted(userId, doc) {
}
}
function publishChekListUncompleted(userId, doc) {
const card = ReactiveCache.getCard(doc.cardId);
async function publishChekListUncompleted(userId, doc) {
const card = await ReactiveCache.getCard(doc.cardId);
const boardId = card.boardId;
const checklistId = doc.checklistId;
const checkList = ReactiveCache.getChecklist(checklistId);
const checkList = await ReactiveCache.getChecklist(checklistId);
// BUGS in IFTTT Rules: https://github.com/wekan/wekan/issues/1972
// Currently in checklist all are set as uncompleted/not checked,
// IFTTT Rule does not move card to other list.
// If following line is negated/changed to:
// if(!checkList.isFinished()){
// if(!isChecklistFinished){
// then unchecking of any checkbox will move card to other list,
// even when all checkboxes are not yet unchecked.
// What is correct code for only moving when all in list is unchecked?
@ -195,7 +199,10 @@ function publishChekListUncompleted(userId, doc) {
// find . | xargs grep 'count' -sl | grep -v .meteor | grep -v node_modules | grep -v .build
// Maybe something related here?
// wekan/client/components/rules/triggers/checklistTriggers.js
if (checkList.isFinished()) {
const uncheckItems = await ReactiveCache.getChecklistItems({ checklistId });
const isChecklistFinished = checkList.hideAllChecklistItems ||
(uncheckItems.length > 0 && uncheckItems.length === uncheckItems.filter(i => i.isFinished).length);
if (isChecklistFinished) {
const act = {
userId,
activityType: 'uncompleteChecklist',
@ -218,22 +225,22 @@ if (Meteor.isServer) {
await ChecklistItems._collection.createIndexAsync({ cardId: 1 });
});
ChecklistItems.after.update((userId, doc, fieldNames) => {
publishCheckActivity(userId, doc);
publishChekListCompleted(userId, doc, fieldNames);
ChecklistItems.after.update(async (userId, doc, fieldNames) => {
await publishCheckActivity(userId, doc);
await publishChekListCompleted(userId, doc, fieldNames);
});
ChecklistItems.before.update((userId, doc, fieldNames) => {
publishChekListUncompleted(userId, doc, fieldNames);
ChecklistItems.before.update(async (userId, doc, fieldNames) => {
await publishChekListUncompleted(userId, doc, fieldNames);
});
ChecklistItems.after.insert((userId, doc) => {
itemCreation(userId, doc);
ChecklistItems.after.insert(async (userId, doc) => {
await itemCreation(userId, doc);
});
ChecklistItems.before.remove((userId, doc) => {
ChecklistItems.before.remove(async (userId, doc) => {
itemRemover(userId, doc);
const card = ReactiveCache.getCard(doc.cardId);
const card = await ReactiveCache.getCard(doc.cardId);
const boardId = card.boardId;
Activities.insert({
userId,
@ -264,15 +271,15 @@ if (Meteor.isServer) {
JsonRoutes.add(
'GET',
'/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId',
function(req, res) {
async function(req, res) {
const paramBoardId = req.params.boardId;
const paramCardId = req.params.cardId;
const paramChecklistId = req.params.checklistId;
const paramItemId = req.params.itemId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
const checklistItem = ReactiveCache.getChecklistItem(paramItemId);
const checklistItem = await ReactiveCache.getChecklistItem(paramItemId);
if (checklistItem && checklistItem.cardId === paramCardId && checklistItem.checklistId === paramChecklistId) {
const card = ReactiveCache.getCard(checklistItem.cardId);
const card = await ReactiveCache.getCard(checklistItem.cardId);
if (card && card.boardId === paramBoardId) {
JsonRoutes.sendResult(res, {
code: 200,
@ -305,17 +312,17 @@ if (Meteor.isServer) {
JsonRoutes.add(
'POST',
'/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items',
function(req, res) {
async function(req, res) {
const paramBoardId = req.params.boardId;
const paramChecklistId = req.params.checklistId;
const paramCardId = req.params.cardId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
const checklist = ReactiveCache.getChecklist({
const checklist = await ReactiveCache.getChecklist({
_id: paramChecklistId,
cardId: paramCardId,
});
if (checklist) {
const card = ReactiveCache.getCard(paramCardId);
const card = await ReactiveCache.getCard(paramCardId);
if (card && card.boardId === paramBoardId) {
const id = ChecklistItems.insert({
cardId: paramCardId,
@ -359,21 +366,21 @@ if (Meteor.isServer) {
JsonRoutes.add(
'PUT',
'/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId',
function(req, res) {
async function(req, res) {
const paramBoardId = req.params.boardId;
const paramCardId = req.params.cardId;
const paramChecklistId = req.params.checklistId;
const paramItemId = req.params.itemId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
const checklistItem = ReactiveCache.getChecklistItem(paramItemId);
const checklistItem = await ReactiveCache.getChecklistItem(paramItemId);
if (!checklistItem || checklistItem.cardId !== paramCardId || checklistItem.checklistId !== paramChecklistId) {
JsonRoutes.sendResult(res, {
code: 404,
});
return;
}
const card = ReactiveCache.getCard(checklistItem.cardId);
const card = await ReactiveCache.getCard(checklistItem.cardId);
if (!card || card.boardId !== paramBoardId) {
JsonRoutes.sendResult(res, {
code: 404,
@ -427,21 +434,21 @@ if (Meteor.isServer) {
JsonRoutes.add(
'DELETE',
'/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId',
function(req, res) {
async function(req, res) {
const paramBoardId = req.params.boardId;
const paramCardId = req.params.cardId;
const paramChecklistId = req.params.checklistId;
const paramItemId = req.params.itemId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
const checklistItem = ReactiveCache.getChecklistItem(paramItemId);
const checklistItem = await ReactiveCache.getChecklistItem(paramItemId);
if (!checklistItem || checklistItem.cardId !== paramCardId || checklistItem.checklistId !== paramChecklistId) {
JsonRoutes.sendResult(res, {
code: 404,
});
return;
}
const card = ReactiveCache.getCard(checklistItem.cardId);
const card = await ReactiveCache.getCard(checklistItem.cardId);
if (!card || card.boardId !== paramBoardId) {
JsonRoutes.sendResult(res, {
code: 404,

View file

@ -88,19 +88,18 @@ Checklists.attachSchema(
);
Checklists.helpers({
copy(newCardId) {
async copy(newCardId) {
let copyObj = Object.assign({}, this);
delete copyObj._id;
copyObj.cardId = newCardId;
const newChecklistId = Checklists.insert(copyObj);
ReactiveCache.getChecklistItems({ checklistId: this._id }).forEach(function(
item,
) {
const items = await ReactiveCache.getChecklistItems({ checklistId: this._id });
for (const item of items) {
item._id = null;
item.checklistId = newChecklistId;
item.cardId = newCardId;
ChecklistItems.insert(item);
});
}
},
itemCount() {
@ -151,13 +150,13 @@ Checklists.helpers({
return ret;
},
async checkAllItems() {
const checkItems = ReactiveCache.getChecklistItems({ checklistId: this._id });
const checkItems = await ReactiveCache.getChecklistItems({ checklistId: this._id });
for (const item of checkItems) {
await item.check();
}
},
async uncheckAllItems() {
const checkItems = ReactiveCache.getChecklistItems({ checklistId: this._id });
const checkItems = await ReactiveCache.getChecklistItems({ checklistId: this._id });
for (const item of checkItems) {
await item.uncheck();
}
@ -198,15 +197,15 @@ Checklists.helpers({
Checklists.allow({
insert(userId, doc) {
// ReadOnly users cannot create checklists
return allowIsBoardMemberWithWriteAccessByCard(userId, ReactiveCache.getCard(doc.cardId));
return allowIsBoardMemberWithWriteAccessByCard(userId, Cards.findOne(doc.cardId));
},
update(userId, doc) {
// ReadOnly users cannot edit checklists
return allowIsBoardMemberWithWriteAccessByCard(userId, ReactiveCache.getCard(doc.cardId));
return allowIsBoardMemberWithWriteAccessByCard(userId, Cards.findOne(doc.cardId));
},
remove(userId, doc) {
// ReadOnly users cannot delete checklists
return allowIsBoardMemberWithWriteAccessByCard(userId, ReactiveCache.getCard(doc.cardId));
return allowIsBoardMemberWithWriteAccessByCard(userId, Cards.findOne(doc.cardId));
},
fetch: ['userId', 'cardId'],
});
@ -221,22 +220,22 @@ Checklists.before.insert((userId, doc) => {
if (Meteor.isServer) {
Meteor.methods({
moveChecklist(checklistId, newCardId) {
async moveChecklist(checklistId, newCardId) {
check(checklistId, String);
check(newCardId, String);
const checklist = ReactiveCache.getChecklist(checklistId);
const checklist = await ReactiveCache.getChecklist(checklistId);
if (!checklist) {
throw new Meteor.Error('checklist-not-found', 'Checklist not found');
}
const newCard = ReactiveCache.getCard(newCardId);
const newCard = await ReactiveCache.getCard(newCardId);
if (!newCard) {
throw new Meteor.Error('card-not-found', 'Target card not found');
}
// Check permissions on both source and target cards
const sourceCard = ReactiveCache.getCard(checklist.cardId);
const sourceCard = await ReactiveCache.getCard(checklist.cardId);
if (!allowIsBoardMemberByCard(this.userId, sourceCard)) {
throw new Meteor.Error('not-authorized', 'Not authorized to move checklist from source card');
}
@ -245,22 +244,24 @@ if (Meteor.isServer) {
}
// Update activities
ReactiveCache.getActivities({ checklistId }).forEach(activity => {
const activities = await ReactiveCache.getActivities({ checklistId });
for (const activity of activities) {
Activities.update(activity._id, {
$set: {
cardId: newCardId,
},
});
});
}
// Update checklist items
ReactiveCache.getChecklistItems({ checklistId }).forEach(checklistItem => {
const checklistItems = await ReactiveCache.getChecklistItems({ checklistId });
for (const checklistItem of checklistItems) {
ChecklistItems.update(checklistItem._id, {
$set: {
cardId: newCardId,
},
});
});
}
// Update the checklist itself
Checklists.update(checklistId, {
@ -278,8 +279,8 @@ if (Meteor.isServer) {
await Checklists._collection.createIndexAsync({ cardId: 1, createdAt: 1 });
});
Checklists.after.insert((userId, doc) => {
const card = ReactiveCache.getCard(doc.cardId);
Checklists.after.insert(async (userId, doc) => {
const card = await ReactiveCache.getCard(doc.cardId);
Activities.insert({
userId,
activityType: 'addChecklist',
@ -292,19 +293,19 @@ if (Meteor.isServer) {
});
});
Checklists.before.remove((userId, doc) => {
const activities = ReactiveCache.getActivities({ checklistId: doc._id });
const card = ReactiveCache.getCard(doc.cardId);
Checklists.before.remove(async (userId, doc) => {
const activities = await ReactiveCache.getActivities({ checklistId: doc._id });
const card = await ReactiveCache.getCard(doc.cardId);
if (activities) {
activities.forEach(activity => {
for (const activity of activities) {
Activities.remove(activity._id);
});
}
}
Activities.insert({
userId,
activityType: 'removeChecklist',
cardId: doc.cardId,
boardId: ReactiveCache.getCard(doc.cardId).boardId,
boardId: (await ReactiveCache.getCard(doc.cardId)).boardId,
checklistId: doc._id,
checklistName: doc.title,
listId: card.listId,
@ -326,13 +327,13 @@ if (Meteor.isServer) {
JsonRoutes.add(
'GET',
'/api/boards/:boardId/cards/:cardId/checklists',
function(req, res) {
async function(req, res) {
const paramBoardId = req.params.boardId;
const paramCardId = req.params.cardId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
// Verify the card belongs to the board
const card = ReactiveCache.getCard({
const card = await ReactiveCache.getCard({
_id: paramCardId,
boardId: paramBoardId,
});
@ -344,7 +345,7 @@ if (Meteor.isServer) {
return;
}
const checklists = ReactiveCache.getChecklists({ cardId: paramCardId }).map(function(
const checklists = (await ReactiveCache.getChecklists({ cardId: paramCardId })).map(function(
doc,
) {
return {
@ -384,14 +385,14 @@ if (Meteor.isServer) {
JsonRoutes.add(
'GET',
'/api/boards/:boardId/cards/:cardId/checklists/:checklistId',
function(req, res) {
async function(req, res) {
const paramBoardId = req.params.boardId;
const paramChecklistId = req.params.checklistId;
const paramCardId = req.params.cardId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
// Verify the card belongs to the board
const card = ReactiveCache.getCard({
const card = await ReactiveCache.getCard({
_id: paramCardId,
boardId: paramBoardId,
});
@ -403,14 +404,14 @@ if (Meteor.isServer) {
return;
}
const checklist = ReactiveCache.getChecklist({
const checklist = await ReactiveCache.getChecklist({
_id: paramChecklistId,
cardId: paramCardId,
});
if (checklist) {
checklist.items = ReactiveCache.getChecklistItems({
checklist.items = (await ReactiveCache.getChecklistItems({
checklistId: checklist._id,
}).map(function(doc) {
})).map(function(doc) {
return {
_id: doc._id,
title: doc.title,
@ -442,19 +443,19 @@ if (Meteor.isServer) {
JsonRoutes.add(
'POST',
'/api/boards/:boardId/cards/:cardId/checklists',
function(req, res) {
async function(req, res) {
// Check user is logged in
//Authentication.checkLoggedIn(req.userId);
const paramBoardId = req.params.boardId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
// Check user has permission to add checklist to the card
const board = ReactiveCache.getBoard(paramBoardId);
const board = await ReactiveCache.getBoard(paramBoardId);
const addPermission = allowIsBoardMemberCommentOnly(req.userId, board);
Authentication.checkAdminOrCondition(req.userId, addPermission);
const paramCardId = req.params.cardId;
// Verify the card belongs to the board
const card = ReactiveCache.getCard({
const card = await ReactiveCache.getCard({
_id: paramCardId,
boardId: paramBoardId,
});
@ -516,14 +517,14 @@ if (Meteor.isServer) {
JsonRoutes.add(
'DELETE',
'/api/boards/:boardId/cards/:cardId/checklists/:checklistId',
function(req, res) {
async function(req, res) {
const paramBoardId = req.params.boardId;
const paramCardId = req.params.cardId;
const paramChecklistId = req.params.checklistId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
// Verify the card belongs to the board
const card = ReactiveCache.getCard({
const card = await ReactiveCache.getCard({
_id: paramCardId,
boardId: paramBoardId,
});
@ -536,7 +537,7 @@ if (Meteor.isServer) {
}
// Verify the checklist exists and belongs to the card
const checklist = ReactiveCache.getChecklist({
const checklist = await ReactiveCache.getChecklist({
_id: paramChecklistId,
cardId: paramCardId,
});

View file

@ -247,7 +247,7 @@ export class CsvCreator {
this.swimlane = swimlaneId;
}
createLists(csvData, boardId) {
async createLists(csvData, boardId) {
let numOfCreatedLists = 0;
for (let i = 1; i < csvData.length; i++) {
const listToCreate = {
@ -256,7 +256,7 @@ export class CsvCreator {
createdAt: this._now(),
};
if (csvData[i][this.fieldIndex.stage]) {
const existingList = ReactiveCache.getLists({
const existingList = await ReactiveCache.getLists({
title: csvData[i][this.fieldIndex.stage],
boardId,
});
@ -279,7 +279,7 @@ export class CsvCreator {
}
}
createCards(csvData, boardId) {
async createCards(csvData, boardId) {
for (let i = 1; i < csvData.length; i++) {
const cardToCreate = {
archived: false,
@ -321,7 +321,7 @@ export class CsvCreator {
}
// add the labels
if (csvData[i][this.fieldIndex.labels]) {
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
for (const importedLabel of csvData[i][this.fieldIndex.labels].split(
' ',
)) {
@ -388,15 +388,15 @@ export class CsvCreator {
Meteor.settings.public &&
Meteor.settings.public.sandstorm;
if (isSandstorm && currentBoardId) {
const currentBoard = ReactiveCache.getBoard(currentBoardId);
const currentBoard = await ReactiveCache.getBoard(currentBoardId);
await currentBoard.archive();
}
this.mapHeadertoCardFieldIndex(board[0]);
const boardId = this.createBoard(board);
this.createLists(board, boardId);
await this.createLists(board, boardId);
this.createSwimlanes(boardId);
this.createCustomFields(boardId);
this.createCards(board, boardId);
await this.createCards(board, boardId);
return boardId;
}
}

View file

@ -167,25 +167,25 @@ CustomFields.allow({
insert(userId, doc) {
return allowIsAnyBoardMember(
userId,
ReactiveCache.getBoards({
Boards.find({
_id: { $in: doc.boardIds },
}),
}).fetch(),
);
},
update(userId, doc) {
return allowIsAnyBoardMember(
userId,
ReactiveCache.getBoards({
Boards.find({
_id: { $in: doc.boardIds },
}),
}).fetch(),
);
},
remove(userId, doc) {
return allowIsAnyBoardMember(
userId,
ReactiveCache.getBoards({
Boards.find({
_id: { $in: doc.boardIds },
}),
}).fetch(),
);
},
fetch: ['userId', 'boardIds'],
@ -214,9 +214,9 @@ function customFieldDeletion(userId, doc) {
// This has some bug, it does not show edited customField value at Outgoing Webhook,
// instead it shows undefined, and no listId and swimlaneId.
function customFieldEdit(userId, doc) {
const card = ReactiveCache.getCard(doc.cardId);
const customFieldValue = ReactiveCache.getActivity({ customFieldId: doc._id }).value;
async function customFieldEdit(userId, doc) {
const card = await ReactiveCache.getCard(doc.cardId);
const customFieldValue = (await ReactiveCache.getActivity({ customFieldId: doc._id })).value;
Activities.insert({
userId,
activityType: 'setCustomField',
@ -242,14 +242,14 @@ if (Meteor.isServer) {
}
});
CustomFields.before.update((userId, doc, fieldNames, modifier) => {
CustomFields.before.update(async (userId, doc, fieldNames, modifier) => {
if (_.contains(fieldNames, 'boardIds') && modifier.$pull) {
Cards.update(
{ boardId: modifier.$pull.boardIds, 'customFields._id': doc._id },
{ $pull: { customFields: { _id: doc._id } } },
{ multi: true },
);
customFieldEdit(userId, doc);
await customFieldEdit(userId, doc);
Activities.remove({
customFieldId: doc._id,
boardId: modifier.$pull.boardIds,
@ -296,7 +296,7 @@ if (Meteor.isServer) {
* name: string,
* type: string}]
*/
JsonRoutes.add('GET', '/api/boards/:boardId/custom-fields', function(
JsonRoutes.add('GET', '/api/boards/:boardId/custom-fields', async function(
req,
res,
) {
@ -304,7 +304,7 @@ if (Meteor.isServer) {
Authentication.checkBoardAccess(req.userId, paramBoardId);
JsonRoutes.sendResult(res, {
code: 200,
data: ReactiveCache.getCustomFields({ boardIds: { $in: [paramBoardId] } }).map(
data: (await ReactiveCache.getCustomFields({ boardIds: { $in: [paramBoardId] } })).map(
function(cf) {
return {
_id: cf._id,
@ -328,13 +328,13 @@ if (Meteor.isServer) {
JsonRoutes.add(
'GET',
'/api/boards/:boardId/custom-fields/:customFieldId',
function(req, res) {
async function(req, res) {
const paramBoardId = req.params.boardId;
const paramCustomFieldId = req.params.customFieldId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
JsonRoutes.sendResult(res, {
code: 200,
data: ReactiveCache.getCustomField({
data: await ReactiveCache.getCustomField({
_id: paramCustomFieldId,
boardIds: { $in: [paramBoardId] },
}),
@ -356,13 +356,13 @@ if (Meteor.isServer) {
* @param {boolean} showSumAtTopOfList should the sum of the custom fields be shown at top of list?
* @return_type {_id: string}
*/
JsonRoutes.add('POST', '/api/boards/:boardId/custom-fields', function(
JsonRoutes.add('POST', '/api/boards/:boardId/custom-fields', async function(
req,
res,
) {
const paramBoardId = req.params.boardId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
const board = ReactiveCache.getBoard(paramBoardId);
const board = await ReactiveCache.getBoard(paramBoardId);
const id = CustomFields.direct.insert({
name: req.body.name,
type: req.body.type,
@ -374,7 +374,7 @@ if (Meteor.isServer) {
boardIds: [board._id],
});
const customField = ReactiveCache.getCustomField({
const customField = await ReactiveCache.getCustomField({
_id: id,
boardIds: { $in: [paramBoardId] },
});

View file

@ -27,14 +27,14 @@ if (Meteor.isServer) {
* @param {string} boardId the ID of the board we are exporting
* @param {string} authToken the loginToken
*/
JsonRoutes.add('get', '/api/boards/:boardId/export', function (req, res) {
JsonRoutes.add('get', '/api/boards/:boardId/export', async function (req, res) {
const boardId = req.params.boardId;
let user = null;
let impersonateDone = false;
let adminId = null;
// First check if board exists and is public to avoid unnecessary authentication
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
JsonRoutes.sendResult(res, 404);
return;
@ -46,7 +46,7 @@ if (Meteor.isServer) {
const exporter = new Exporter(boardId);
JsonRoutes.sendResult(res, {
code: 200,
data: exporter.build(),
data: await exporter.build(),
});
return;
}
@ -64,18 +64,18 @@ if (Meteor.isServer) {
}
const hashToken = Accounts._hashLoginToken(loginToken);
user = ReactiveCache.getUser({
user = await ReactiveCache.getUser({
'services.resume.loginTokens.hashedToken': hashToken,
});
adminId = user._id.toString();
impersonateDone = ReactiveCache.getImpersonatedUser({ adminId: adminId });
impersonateDone = await ReactiveCache.getImpersonatedUser({ adminId: adminId });
} else if (!Meteor.settings.public.sandstorm) {
Authentication.checkUserId(req.userId);
user = ReactiveCache.getUser({ _id: req.userId, isAdmin: true });
user = await ReactiveCache.getUser({ _id: req.userId, isAdmin: true });
}
const exporter = new Exporter(boardId);
if (exporter.canExport(user) || impersonateDone) {
if (await exporter.canExport(user) || impersonateDone) {
if (impersonateDone) {
ImpersonatedUsers.insert({
adminId: adminId,
@ -86,7 +86,7 @@ if (Meteor.isServer) {
JsonRoutes.sendResult(res, {
code: 200,
data: exporter.build(),
data: await exporter.build(),
});
} else {
// we could send an explicit error message, but on the other hand the only
@ -118,7 +118,7 @@ if (Meteor.isServer) {
JsonRoutes.add(
'get',
'/api/boards/:boardId/attachments/:attachmentId/export',
function (req, res) {
async function (req, res) {
const boardId = req.params.boardId;
const attachmentId = req.params.attachmentId;
let user = null;
@ -126,7 +126,7 @@ if (Meteor.isServer) {
let adminId = null;
// First check if board exists and is public to avoid unnecessary authentication
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
JsonRoutes.sendResult(res, 404);
return;
@ -138,7 +138,7 @@ if (Meteor.isServer) {
const exporter = new Exporter(boardId, attachmentId);
JsonRoutes.sendResult(res, {
code: 200,
data: exporter.build(),
data: await exporter.build(),
});
return;
}
@ -156,18 +156,18 @@ if (Meteor.isServer) {
}
const hashToken = Accounts._hashLoginToken(loginToken);
user = ReactiveCache.getUser({
user = await ReactiveCache.getUser({
'services.resume.loginTokens.hashedToken': hashToken,
});
adminId = user._id.toString();
impersonateDone = ReactiveCache.getImpersonatedUser({ adminId: adminId });
impersonateDone = await ReactiveCache.getImpersonatedUser({ adminId: adminId });
} else if (!Meteor.settings.public.sandstorm) {
Authentication.checkUserId(req.userId);
user = ReactiveCache.getUser({ _id: req.userId, isAdmin: true });
user = await ReactiveCache.getUser({ _id: req.userId, isAdmin: true });
}
const exporter = new Exporter(boardId, attachmentId);
if (exporter.canExport(user) || impersonateDone) {
if (await exporter.canExport(user) || impersonateDone) {
if (impersonateDone) {
ImpersonatedUsers.insert({
adminId: adminId,
@ -178,7 +178,7 @@ if (Meteor.isServer) {
}
JsonRoutes.sendResult(res, {
code: 200,
data: exporter.build(),
data: await exporter.build(),
});
} else {
// we could send an explicit error message, but on the other hand the only
@ -203,14 +203,14 @@ if (Meteor.isServer) {
* @param {string} authToken the loginToken
* @param {string} delimiter delimiter to use while building export. Default is comma ','
*/
Picker.route('/api/boards/:boardId/export/csv', function (params, req, res) {
Picker.route('/api/boards/:boardId/export/csv', async function (params, req, res) {
const boardId = params.boardId;
let user = null;
let impersonateDone = false;
let adminId = null;
// First check if board exists and is public to avoid unnecessary authentication
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
res.writeHead(404);
res.end('Board not found');
@ -237,7 +237,7 @@ if (Meteor.isServer) {
// use Uint8Array to prevent from converting bytes to string
res.write(new Uint8Array([0xEF, 0xBB, 0xBF]));
}
res.write(exporter.buildCsv(params.query.delimiter, 'en'));
res.write(await exporter.buildCsv(params.query.delimiter, 'en'));
res.end();
return;
}
@ -256,21 +256,21 @@ if (Meteor.isServer) {
}
const hashToken = Accounts._hashLoginToken(loginToken);
user = ReactiveCache.getUser({
user = await ReactiveCache.getUser({
'services.resume.loginTokens.hashedToken': hashToken,
});
adminId = user._id.toString();
impersonateDone = ReactiveCache.getImpersonatedUser({ adminId: adminId });
impersonateDone = await ReactiveCache.getImpersonatedUser({ adminId: adminId });
} else if (!Meteor.settings.public.sandstorm) {
Authentication.checkUserId(req.userId);
user = ReactiveCache.getUser({
user = await ReactiveCache.getUser({
_id: req.userId,
isAdmin: true,
});
}
const exporter = new Exporter(boardId);
if (exporter.canExport(user) || impersonateDone) {
if (await exporter.canExport(user) || impersonateDone) {
if (impersonateDone) {
let exportType = 'exportCSV';
if( params.query.delimiter == "\t" ) {
@ -303,7 +303,7 @@ if (Meteor.isServer) {
// use Uint8Array to prevent from converting bytes to string
res.write(new Uint8Array([0xEF, 0xBB, 0xBF]));
}
res.write(exporter.buildCsv(params.query.delimiter, userLanguage));
res.write(await exporter.buildCsv(params.query.delimiter, userLanguage));
res.end();
} else {
res.writeHead(403);

View file

@ -30,14 +30,14 @@ runOnServer(function() {
* @param {string} boardId the ID of the board we are exporting
* @param {string} authToken the loginToken
*/
Picker.route('/api/boards/:boardId/exportExcel', function (params, req, res) {
Picker.route('/api/boards/:boardId/exportExcel', async function (params, req, res) {
const boardId = params.boardId;
let user = null;
let impersonateDone = false;
let adminId = null;
// First check if board exists and is public to avoid unnecessary authentication
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
res.end('Board not found');
return;
@ -64,14 +64,14 @@ runOnServer(function() {
}
const hashToken = Accounts._hashLoginToken(loginToken);
user = ReactiveCache.getUser({
user = await ReactiveCache.getUser({
'services.resume.loginTokens.hashedToken': hashToken,
});
adminId = user._id.toString();
impersonateDone = ReactiveCache.getImpersonatedUser({ adminId: adminId });
impersonateDone = await ReactiveCache.getImpersonatedUser({ adminId: adminId });
} else if (!Meteor.settings.public.sandstorm) {
Authentication.checkUserId(req.userId);
user = ReactiveCache.getUser({
user = await ReactiveCache.getUser({
_id: req.userId,
isAdmin: true,
});

View file

@ -30,7 +30,7 @@ runOnServer(function() {
* @param {string} boardId the ID of the board we are exporting
* @param {string} authToken the loginToken
*/
Picker.route('/api/boards/:boardId/lists/:listId/cards/:cardId/exportPDF', function (params, req, res) {
Picker.route('/api/boards/:boardId/lists/:listId/cards/:cardId/exportPDF', async function (params, req, res) {
const boardId = params.boardId;
const paramListId = req.params.listId;
const paramCardId = req.params.cardId;
@ -39,7 +39,7 @@ runOnServer(function() {
let adminId = null;
// First check if board exists and is public to avoid unnecessary authentication
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
res.end('Board not found');
return;
@ -66,14 +66,14 @@ runOnServer(function() {
}
const hashToken = Accounts._hashLoginToken(loginToken);
user = ReactiveCache.getUser({
user = await ReactiveCache.getUser({
'services.resume.loginTokens.hashedToken': hashToken,
});
adminId = user._id.toString();
impersonateDone = ReactiveCache.getImpersonatedUser({ adminId: adminId });
impersonateDone = await ReactiveCache.getImpersonatedUser({ adminId: adminId });
} else if (!Meteor.settings.public.sandstorm) {
Authentication.checkUserId(req.userId);
user = ReactiveCache.getUser({
user = await ReactiveCache.getUser({
_id: req.userId,
isAdmin: true,
});

View file

@ -34,7 +34,7 @@ export class Exporter {
this._attachmentId = attachmentId;
}
build() {
async build() {
const fs = Npm.require('fs');
const os = Npm.require('os');
const path = Npm.require('path');
@ -55,7 +55,7 @@ export class Exporter {
};
_.extend(
result,
ReactiveCache.getBoard(this._boardId, {
await ReactiveCache.getBoard(this._boardId, {
fields: {
stars: 0,
},
@ -97,7 +97,7 @@ export class Exporter {
const byBoardAndAttachment = this._attachmentId
? { boardId: this._boardId, _id: this._attachmentId }
: byBoard;
result.attachments = ReactiveCache.getAttachments(byBoardAndAttachment)
result.attachments = (await ReactiveCache.getAttachments(byBoardAndAttachment))
.map((attachment) => {
let filebase64 = null;
filebase64 = getBase64DataSync(attachment);
@ -116,41 +116,41 @@ export class Exporter {
return result.attachments.length > 0 ? result.attachments[0] : {};
}
result.lists = ReactiveCache.getLists(byBoard, noBoardId);
result.cards = ReactiveCache.getCards(byBoardNoLinked, noBoardId);
result.swimlanes = ReactiveCache.getSwimlanes(byBoard, noBoardId);
result.customFields = ReactiveCache.getCustomFields(
result.lists = await ReactiveCache.getLists(byBoard, noBoardId);
result.cards = await ReactiveCache.getCards(byBoardNoLinked, noBoardId);
result.swimlanes = await ReactiveCache.getSwimlanes(byBoard, noBoardId);
result.customFields = await ReactiveCache.getCustomFields(
{ boardIds: this._boardId },
{ fields: { boardIds: 0 } },
);
result.comments = ReactiveCache.getCardComments(byBoard, noBoardId);
result.activities = ReactiveCache.getActivities(byBoard, noBoardId);
result.rules = ReactiveCache.getRules(byBoard, noBoardId);
result.comments = await ReactiveCache.getCardComments(byBoard, noBoardId);
result.activities = await ReactiveCache.getActivities(byBoard, noBoardId);
result.rules = await ReactiveCache.getRules(byBoard, noBoardId);
result.checklists = [];
result.checklistItems = [];
result.subtaskItems = [];
result.triggers = [];
result.actions = [];
result.cards.forEach((card) => {
for (const card of result.cards) {
result.checklists.push(
...ReactiveCache.getChecklists({
...await ReactiveCache.getChecklists({
cardId: card._id,
}),
);
result.checklistItems.push(
...ReactiveCache.getChecklistItems({
...await ReactiveCache.getChecklistItems({
cardId: card._id,
}),
);
result.subtaskItems.push(
...ReactiveCache.getCards({
...await ReactiveCache.getCards({
parentId: card._id,
}),
);
});
result.rules.forEach((rule) => {
}
for (const rule of result.rules) {
result.triggers.push(
...ReactiveCache.getTriggers(
...await ReactiveCache.getTriggers(
{
_id: rule.triggerId,
},
@ -158,14 +158,14 @@ export class Exporter {
),
);
result.actions.push(
...ReactiveCache.getActions(
...await ReactiveCache.getActions(
{
_id: rule.actionId,
},
noBoardId,
),
);
});
}
// we also have to export some user data - as the other elements only
// include id but we have to be careful:
@ -211,7 +211,7 @@ export class Exporter {
'profile.avatarUrl': 1,
},
};
result.users = ReactiveCache.getUsers(byUserIds, userFields)
result.users = (await ReactiveCache.getUsers(byUserIds, userFields))
.map((user) => {
// user avatar is stored as a relative url, we export absolute
if ((user.profile || {}).avatarUrl) {
@ -222,8 +222,8 @@ export class Exporter {
return result;
}
buildCsv(userDelimiter = ',', userLanguage='en') {
const result = this.build();
async buildCsv(userDelimiter = ',', userLanguage='en') {
const result = await this.build();
const columnHeaders = [];
const cardRows = [];
@ -398,8 +398,8 @@ export class Exporter {
return Papa.unparse(cardRows, papaconfig);
}
canExport(user) {
const board = ReactiveCache.getBoard(this._boardId);
async canExport(user) {
const board = await ReactiveCache.getBoard(this._boardId);
return board && board.isVisibleBy(user);
}
}

View file

@ -102,9 +102,9 @@ Integrations.Const = {
};
const permissionHelper = {
allow(userId, doc) {
const user = ReactiveCache.getUser(userId);
const isAdmin = user && ReactiveCache.getCurrentUser().isAdmin;
return isAdmin || allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId));
const user = Meteor.users.findOne(userId);
const isAdmin = user && user.isAdmin;
return isAdmin || allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
},
};
Integrations.allow({
@ -134,7 +134,7 @@ if (Meteor.isServer) {
* @param {string} boardId the board ID
* @return_type [Integrations]
*/
JsonRoutes.add('GET', '/api/boards/:boardId/integrations', function(
JsonRoutes.add('GET', '/api/boards/:boardId/integrations', async function(
req,
res,
) {
@ -142,10 +142,10 @@ if (Meteor.isServer) {
const paramBoardId = req.params.boardId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
const data = ReactiveCache.getIntegrations(
const data = (await ReactiveCache.getIntegrations(
{ boardId: paramBoardId },
{ fields: { token: 0 } },
).map(function(doc) {
)).map(function(doc) {
return doc;
});
@ -166,7 +166,7 @@ if (Meteor.isServer) {
* @param {string} intId the integration ID
* @return_type Integrations
*/
JsonRoutes.add('GET', '/api/boards/:boardId/integrations/:intId', function(
JsonRoutes.add('GET', '/api/boards/:boardId/integrations/:intId', async function(
req,
res,
) {
@ -177,7 +177,7 @@ if (Meteor.isServer) {
JsonRoutes.sendResult(res, {
code: 200,
data: ReactiveCache.getIntegration(
data: await ReactiveCache.getIntegration(
{ _id: paramIntId, boardId: paramBoardId },
{ fields: { token: 0 } },
),
@ -310,7 +310,7 @@ if (Meteor.isServer) {
JsonRoutes.add(
'DELETE',
'/api/boards/:boardId/integrations/:intId/activities',
function(req, res) {
async function(req, res) {
try {
const paramBoardId = req.params.boardId;
const paramIntId = req.params.intId;
@ -324,7 +324,7 @@ if (Meteor.isServer) {
JsonRoutes.sendResult(res, {
code: 200,
data: ReactiveCache.getIntegration(
data: await ReactiveCache.getIntegration(
{ _id: paramIntId, boardId: paramBoardId },
{ fields: { _id: 1, activities: 1 } },
),
@ -350,7 +350,7 @@ if (Meteor.isServer) {
JsonRoutes.add(
'POST',
'/api/boards/:boardId/integrations/:intId/activities',
function(req, res) {
async function(req, res) {
try {
const paramBoardId = req.params.boardId;
const paramIntId = req.params.intId;
@ -364,7 +364,7 @@ if (Meteor.isServer) {
JsonRoutes.sendResult(res, {
code: 200,
data: ReactiveCache.getIntegration(
data: await ReactiveCache.getIntegration(
{ _id: paramIntId, boardId: paramBoardId },
{ fields: { _id: 1, activities: 1 } },
),

View file

@ -580,8 +580,8 @@ export const moveToStorage = function(fileObj, storageDestination, fileStoreStra
});
};
export const copyFile = function(fileObj, newCardId, fileStoreStrategyFactory) {
const newCard = ReactiveCache.getCard(newCardId);
export const copyFile = async function(fileObj, newCardId, fileStoreStrategyFactory) {
const newCard = await ReactiveCache.getCard(newCardId);
Object.keys(fileObj.versions).forEach(versionName => {
const strategyRead = fileStoreStrategyFactory.getFileStrategy(fileObj, versionName);
const readStream = strategyRead.getReadStream();

View file

@ -182,15 +182,15 @@ Lists.attachSchema(
Lists.allow({
insert(userId, doc) {
// ReadOnly and CommentOnly users cannot create lists
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId));
},
update(userId, doc) {
// ReadOnly and CommentOnly users cannot edit lists
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId));
},
remove(userId, doc) {
// ReadOnly and CommentOnly users cannot delete lists
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId));
},
fetch: ['boardId'],
});
@ -203,7 +203,7 @@ Lists.helpers({
this.swimlaneId = swimlaneId;
let _id = null;
const existingListWithSameName = ReactiveCache.getList({
const existingListWithSameName = await ReactiveCache.getList({
boardId,
title: this.title,
archived: false,
@ -213,11 +213,11 @@ Lists.helpers({
} else {
delete this._id;
this.swimlaneId = swimlaneId; // Set the target swimlane for the copied list
_id = Lists.insert(this);
_id = await Lists.insertAsync(this);
}
// Copy all cards in list
const cards = ReactiveCache.getCards({
const cards = await ReactiveCache.getCards({
swimlaneId: oldSwimlaneId,
listId: oldId,
archived: false,
@ -230,7 +230,7 @@ Lists.helpers({
},
async move(boardId, swimlaneId) {
const boardList = ReactiveCache.getList({
const boardList = await ReactiveCache.getList({
boardId,
title: this.title,
archived: false,
@ -238,13 +238,13 @@ Lists.helpers({
let listId;
if (boardList) {
listId = boardList._id;
for (const card of this.cards()) {
for (const card of await this.cards()) {
await card.move(boardId, this._id, boardList._id);
}
} else {
console.log('list.title:', this.title);
console.log('boardList:', boardList);
listId = Lists.insert({
listId = await Lists.insertAsync({
title: this.title,
boardId,
type: this.type,
@ -254,7 +254,7 @@ Lists.helpers({
});
}
for (const card of this.cards(swimlaneId)) {
for (const card of await this.cards(swimlaneId)) {
await card.move(boardId, swimlaneId, listId);
}
},
@ -364,7 +364,7 @@ Lists.helpers({
async archive() {
if (this.isTemplateList()) {
for (const card of this.cards()) {
for (const card of await this.cards()) {
await card.archive();
}
}
@ -373,7 +373,7 @@ Lists.helpers({
async restore() {
if (this.isTemplateList()) {
for (const card of this.allCards()) {
for (const card of await this.allCards()) {
await card.restore();
}
}
@ -397,23 +397,25 @@ Lists.helpers({
},
});
Lists.userArchivedLists = userId => {
return ReactiveCache.getLists({
boardId: { $in: Boards.userBoardIds(userId, null) },
Lists.userArchivedLists = async userId => {
return await ReactiveCache.getLists({
boardId: { $in: await Boards.userBoardIds(userId, null) },
archived: true,
})
};
Lists.userArchivedListIds = () => {
return Lists.userArchivedLists().map(list => { return list._id; });
Lists.userArchivedListIds = async () => {
const lists = await Lists.userArchivedLists();
return lists.map(list => { return list._id; });
};
Lists.archivedLists = () => {
return ReactiveCache.getLists({ archived: true });
Lists.archivedLists = async () => {
return await ReactiveCache.getLists({ archived: true });
};
Lists.archivedListIds = () => {
return Lists.archivedLists().map(list => {
Lists.archivedListIds = async () => {
const lists = await Lists.archivedLists();
return lists.map(list => {
return list._id;
});
};
@ -427,12 +429,12 @@ Meteor.methods({
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const list = ReactiveCache.getList(listId);
const list = await ReactiveCache.getList(listId);
if (!list) {
throw new Meteor.Error('list-not-found', 'List not found');
}
const board = ReactiveCache.getBoard(list.boardId);
const board = await ReactiveCache.getBoard(list.boardId);
if (!board || !board.hasAdmin(this.userId)) {
throw new Meteor.Error('not-authorized', 'You must be a board admin to modify WIP limits.');
}
@ -450,63 +452,62 @@ Meteor.methods({
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const list = ReactiveCache.getList(listId);
const list = await ReactiveCache.getList(listId);
if (!list) {
throw new Meteor.Error('list-not-found', 'List not found');
}
const board = ReactiveCache.getBoard(list.boardId);
const board = await ReactiveCache.getBoard(list.boardId);
if (!board || !board.hasAdmin(this.userId)) {
throw new Meteor.Error('not-authorized', 'You must be a board admin to modify WIP limits.');
}
if (list.getWipLimit('value') === 0) {
if ((await list.getWipLimit('value')) === 0) {
await list.setWipLimit(1);
}
list.toggleWipLimit(!list.getWipLimit('enabled'));
await list.toggleWipLimit(!(await list.getWipLimit('enabled')));
},
enableSoftLimit(listId) {
async enableSoftLimit(listId) {
check(listId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const list = ReactiveCache.getList(listId);
const list = await ReactiveCache.getList(listId);
if (!list) {
throw new Meteor.Error('list-not-found', 'List not found');
}
const board = ReactiveCache.getBoard(list.boardId);
const board = await ReactiveCache.getBoard(list.boardId);
if (!board || !board.hasAdmin(this.userId)) {
throw new Meteor.Error('not-authorized', 'You must be a board admin to modify WIP limits.');
}
list.toggleSoftLimit(!list.getWipLimit('soft'));
await list.toggleSoftLimit(!(await list.getWipLimit('soft')));
},
myLists() {
async myLists() {
// my lists
return _.uniq(
ReactiveCache.getLists(
{
boardId: { $in: Boards.userBoardIds(this.userId) },
archived: false,
},
{
fields: { title: 1 },
},
).map(list => list.title),
).sort();
const lists = await ReactiveCache.getLists(
{
boardId: { $in: await Boards.userBoardIds(this.userId) },
archived: false,
},
{
fields: { title: 1 },
},
);
return _.uniq(lists.map(list => list.title)).sort();
},
updateListSort(listId, boardId, updateData) {
async updateListSort(listId, boardId, updateData) {
check(listId, String);
check(boardId, String);
check(updateData, Object);
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
throw new Meteor.Error('board-not-found', 'Board not found');
}
@ -519,7 +520,7 @@ Meteor.methods({
}
}
const list = ReactiveCache.getList(listId);
const list = await ReactiveCache.getList(listId);
if (!list) {
throw new Meteor.Error('list-not-found', 'List not found');
}
@ -532,14 +533,14 @@ Meteor.methods({
});
if (updateData.swimlaneId) {
const swimlane = ReactiveCache.getSwimlane(updateData.swimlaneId);
const swimlane = await ReactiveCache.getSwimlane(updateData.swimlaneId);
if (!swimlane || swimlane.boardId !== boardId) {
throw new Meteor.Error('invalid-swimlane', 'Invalid swimlane for this board');
}
}
Lists.update(
{ _id: listId, boardId },
await Lists.updateAsync(
listId,
{
$set: {
...updateData,
@ -586,14 +587,14 @@ Lists.after.insert((userId, doc) => {
}, 100);
});
Lists.before.remove((userId, doc) => {
const cards = ReactiveCache.getCards({ listId: doc._id });
Lists.before.remove(async (userId, doc) => {
const cards = await ReactiveCache.getCards({ listId: doc._id });
if (cards) {
cards.forEach(card => {
Cards.remove(card._id);
});
for (const card of cards) {
await Cards.removeAsync(card._id);
}
}
Activities.insert({
await Activities.insertAsync({
userId,
type: 'list',
activityType: 'removeList',
@ -661,14 +662,14 @@ if (Meteor.isServer) {
* @return_type [{_id: string,
* title: string}]
*/
JsonRoutes.add('GET', '/api/boards/:boardId/lists', function(req, res) {
JsonRoutes.add('GET', '/api/boards/:boardId/lists', async function(req, res) {
try {
const paramBoardId = req.params.boardId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
JsonRoutes.sendResult(res, {
code: 200,
data: ReactiveCache.getLists({ boardId: paramBoardId, archived: false }).map(
data: (await ReactiveCache.getLists({ boardId: paramBoardId, archived: false })).map(
function(doc) {
return {
_id: doc._id,
@ -693,7 +694,7 @@ if (Meteor.isServer) {
* @param {string} listId the List ID
* @return_type Lists
*/
JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId', function(
JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId', async function(
req,
res,
) {
@ -703,7 +704,7 @@ if (Meteor.isServer) {
Authentication.checkBoardAccess(req.userId, paramBoardId);
JsonRoutes.sendResult(res, {
code: 200,
data: ReactiveCache.getList({
data: await ReactiveCache.getList({
_id: paramListId,
boardId: paramBoardId,
archived: false,
@ -725,12 +726,12 @@ if (Meteor.isServer) {
* @param {string} title the title of the List
* @return_type {_id: string}
*/
JsonRoutes.add('POST', '/api/boards/:boardId/lists', function(req, res) {
JsonRoutes.add('POST', '/api/boards/:boardId/lists', async function(req, res) {
try {
const paramBoardId = req.params.boardId;
Authentication.checkBoardWriteAccess(req.userId, paramBoardId);
const board = ReactiveCache.getBoard(paramBoardId);
const id = Lists.insert({
const board = await ReactiveCache.getBoard(paramBoardId);
const id = await Lists.insertAsync({
title: req.body.title,
boardId: paramBoardId,
sort: board.lists().length,
@ -766,7 +767,7 @@ if (Meteor.isServer) {
* @param {boolean} [collapsed] whether the list is collapsed
* @return_type {_id: string}
*/
JsonRoutes.add('PUT', '/api/boards/:boardId/lists/:listId', function(
JsonRoutes.add('PUT', '/api/boards/:boardId/lists/:listId', async function(
req,
res,
) {
@ -776,7 +777,7 @@ if (Meteor.isServer) {
let updated = false;
Authentication.checkBoardWriteAccess(req.userId, paramBoardId);
const list = ReactiveCache.getList({
const list = await ReactiveCache.getList({
_id: paramListId,
boardId: paramBoardId,
archived: false,

View file

@ -49,7 +49,7 @@ LockoutSettings.attachSchema(
LockoutSettings.allow({
update(userId) {
const user = ReactiveCache.getUser(userId);
const user = Meteor.users.findOne(userId);
return user && user.isAdmin;
},
});

View file

@ -88,7 +88,7 @@ Org.attachSchema(
if (Meteor.isServer) {
Org.allow({
insert(userId, doc) {
const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser();
const user = Meteor.users.findOne(userId);
if (user?.isAdmin)
return true;
if (!user) {
@ -97,7 +97,7 @@ if (Meteor.isServer) {
return doc._id === userId;
},
update(userId, doc) {
const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser();
const user = Meteor.users.findOne(userId);
if (user?.isAdmin)
return true;
if (!user) {
@ -106,7 +106,7 @@ if (Meteor.isServer) {
return doc._id === userId;
},
remove(userId, doc) {
const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser();
const user = Meteor.users.findOne(userId);
if (user?.isAdmin)
return true;
if (!user) {
@ -119,7 +119,7 @@ if (Meteor.isServer) {
Meteor.methods({
setCreateOrg(
async setCreateOrg(
orgDisplayName,
orgDesc,
orgShortName,
@ -127,7 +127,7 @@ if (Meteor.isServer) {
orgWebsite,
orgIsActive,
) {
if (ReactiveCache.getCurrentUser()?.isAdmin) {
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
check(orgDisplayName, String);
check(orgDesc, String);
check(orgShortName, String);
@ -135,7 +135,7 @@ if (Meteor.isServer) {
check(orgWebsite, String);
check(orgIsActive, Boolean);
const nOrgNames = ReactiveCache.getOrgs({ orgShortName }).length;
const nOrgNames = (await ReactiveCache.getOrgs({ orgShortName })).length;
if (nOrgNames > 0) {
throw new Meteor.Error('orgname-already-taken');
} else {
@ -150,7 +150,7 @@ if (Meteor.isServer) {
}
}
},
setCreateOrgFromOidc(
async setCreateOrgFromOidc(
orgDisplayName,
orgDesc,
orgShortName,
@ -165,7 +165,7 @@ if (Meteor.isServer) {
check(orgWebsite, String);
check(orgIsActive, Boolean);
const nOrgNames = ReactiveCache.getOrgs({ orgShortName }).length;
const nOrgNames = (await ReactiveCache.getOrgs({ orgShortName })).length;
if (nOrgNames > 0) {
throw new Meteor.Error('orgname-already-taken');
} else {
@ -179,19 +179,19 @@ if (Meteor.isServer) {
});
}
},
setOrgDisplayName(org, orgDisplayName) {
if (ReactiveCache.getCurrentUser()?.isAdmin) {
async setOrgDisplayName(org, orgDisplayName) {
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
check(org, Object);
check(orgDisplayName, String);
Org.update(org, {
$set: { orgDisplayName: orgDisplayName },
});
Meteor.call('setUsersOrgsOrgDisplayName', org._id, orgDisplayName);
await Meteor.callAsync('setUsersOrgsOrgDisplayName', org._id, orgDisplayName);
}
},
setOrgDesc(org, orgDesc) {
if (ReactiveCache.getCurrentUser()?.isAdmin) {
async setOrgDesc(org, orgDesc) {
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
check(org, Object);
check(orgDesc, String);
Org.update(org, {
@ -200,8 +200,8 @@ if (Meteor.isServer) {
}
},
setOrgShortName(org, orgShortName) {
if (ReactiveCache.getCurrentUser()?.isAdmin) {
async setOrgShortName(org, orgShortName) {
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
check(org, Object);
check(orgShortName, String);
Org.update(org, {
@ -210,8 +210,8 @@ if (Meteor.isServer) {
}
},
setAutoAddUsersWithDomainName(org, orgAutoAddUsersWithDomainName) {
if (ReactiveCache.getCurrentUser()?.isAdmin) {
async setAutoAddUsersWithDomainName(org, orgAutoAddUsersWithDomainName) {
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
check(org, Object);
check(orgAutoAddUsersWithDomainName, String);
Org.update(org, {
@ -220,8 +220,8 @@ if (Meteor.isServer) {
}
},
setOrgIsActive(org, orgIsActive) {
if (ReactiveCache.getCurrentUser()?.isAdmin) {
async setOrgIsActive(org, orgIsActive) {
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
check(org, Object);
check(orgIsActive, Boolean);
Org.update(org, {
@ -229,7 +229,7 @@ if (Meteor.isServer) {
});
}
},
setOrgAllFieldsFromOidc(
async setOrgAllFieldsFromOidc(
org,
orgDisplayName,
orgDesc,
@ -255,9 +255,9 @@ if (Meteor.isServer) {
orgIsActive: orgIsActive,
},
});
Meteor.call('setUsersOrgsOrgDisplayName', org._id, orgDisplayName);
await Meteor.callAsync('setUsersOrgsOrgDisplayName', org._id, orgDisplayName);
},
setOrgAllFields(
async setOrgAllFields(
org,
orgDisplayName,
orgDesc,
@ -266,7 +266,7 @@ if (Meteor.isServer) {
orgWebsite,
orgIsActive,
) {
if (ReactiveCache.getCurrentUser()?.isAdmin) {
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
check(org, Object);
check(orgDisplayName, String);
check(orgDesc, String);
@ -284,7 +284,7 @@ if (Meteor.isServer) {
orgIsActive: orgIsActive,
},
});
Meteor.call('setUsersOrgsOrgDisplayName', org._id, orgDisplayName);
await Meteor.callAsync('setUsersOrgsOrgDisplayName', org._id, orgDisplayName);
}
},
});

View file

@ -73,13 +73,13 @@ Rules.helpers({
Rules.allow({
insert(userId, doc) {
return allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
},
update(userId, doc) {
return allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
},
remove(userId, doc) {
return allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
},
});

View file

@ -640,8 +640,8 @@ class ExporterCardPDF {
}
canExport(user) {
const board = ReactiveCache.getBoard(this._boardId);
async canExport(user) {
const board = await ReactiveCache.getBoard(this._boardId);
return board && board.isVisibleBy(user);
}
}

View file

@ -31,7 +31,7 @@ class ExporterExcel {
this.userLanguage = userLanguage;
}
build(res) {
async build(res) {
const fs = Npm.require('fs');
const os = Npm.require('os');
const path = Npm.require('path');
@ -56,16 +56,16 @@ class ExporterExcel {
};
_.extend(
result,
ReactiveCache.getBoard(this._boardId, {
await ReactiveCache.getBoard(this._boardId, {
fields: {
stars: 0,
},
}),
);
result.lists = ReactiveCache.getLists(byBoard, noBoardId);
result.cards = ReactiveCache.getCards(byBoardNoLinked, noBoardId);
result.swimlanes = ReactiveCache.getSwimlanes(byBoard, noBoardId);
result.customFields = ReactiveCache.getCustomFields(
result.lists = await ReactiveCache.getLists(byBoard, noBoardId);
result.cards = await ReactiveCache.getCards(byBoardNoLinked, noBoardId);
result.swimlanes = await ReactiveCache.getSwimlanes(byBoard, noBoardId);
result.customFields = await ReactiveCache.getCustomFields(
{
boardIds: {
$in: [this.boardId],
@ -77,34 +77,34 @@ class ExporterExcel {
},
},
);
result.comments = ReactiveCache.getCardComments(byBoard, noBoardId);
result.activities = ReactiveCache.getActivities(byBoard, noBoardId);
result.rules = ReactiveCache.getRules(byBoard, noBoardId);
result.comments = await ReactiveCache.getCardComments(byBoard, noBoardId);
result.activities = await ReactiveCache.getActivities(byBoard, noBoardId);
result.rules = await ReactiveCache.getRules(byBoard, noBoardId);
result.checklists = [];
result.checklistItems = [];
result.subtaskItems = [];
result.triggers = [];
result.actions = [];
result.cards.forEach((card) => {
for (const card of result.cards) {
result.checklists.push(
...ReactiveCache.getChecklists({
...await ReactiveCache.getChecklists({
cardId: card._id,
}),
);
result.checklistItems.push(
...ReactiveCache.getChecklistItems({
...await ReactiveCache.getChecklistItems({
cardId: card._id,
}),
);
result.subtaskItems.push(
...ReactiveCache.getCards({
...await ReactiveCache.getCards({
parentId: card._id,
}),
);
});
result.rules.forEach((rule) => {
}
for (const rule of result.rules) {
result.triggers.push(
...ReactiveCache.getTriggers(
...await ReactiveCache.getTriggers(
{
_id: rule.triggerId,
},
@ -112,14 +112,14 @@ class ExporterExcel {
),
);
result.actions.push(
...ReactiveCache.getActions(
...await ReactiveCache.getActions(
{
_id: rule.actionId,
},
noBoardId,
),
);
});
}
// we also have to export some user data - as the other elements only
// include id but we have to be careful:
@ -169,7 +169,7 @@ class ExporterExcel {
'profile.avatarUrl': 1,
},
};
result.users = ReactiveCache.getUsers(byUserIds, userFields)
result.users = (await ReactiveCache.getUsers(byUserIds, userFields))
.map((user) => {
// user avatar is stored as a relative url, we export absolute
if ((user.profile || {}).avatarUrl) {
@ -905,8 +905,8 @@ class ExporterExcel {
workbook.xlsx.write(res).then(function () {});
}
canExport(user) {
const board = ReactiveCache.getBoard(this._boardId);
async canExport(user) {
const board = await ReactiveCache.getBoard(this._boardId);
return board && board.isVisibleBy(user);
}
}

View file

@ -57,12 +57,12 @@ const getBoardTitleWithMostActivities = (dateWithXdaysAgo, nbLimit) => {
);
};
const getBoards = (boardIds) => {
const ret = ReactiveCache.getBoards({ _id: { $in: boardIds } });
const getBoards = async (boardIds) => {
const ret = await ReactiveCache.getBoards({ _id: { $in: boardIds } });
return ret;
};
Meteor.startup(() => {
WebApp.connectHandlers.use('/metrics', (req, res, next) => {
WebApp.connectHandlers.use('/metrics', async (req, res, next) => {
try {
const ipAddress =
req.headers['x-forwarded-for'] || req.socket.remoteAddress;
@ -95,7 +95,7 @@ Meteor.startup(() => {
metricsRes += '# Number of registered users\n';
// Get number of registered user
resCount = ReactiveCache.getUsers({}).length; // KPI 2
resCount = (await ReactiveCache.getUsers({})).length; // KPI 2
metricsRes += 'wekan_registeredUsers ' + resCount + '\n';
resCount = 0;
@ -103,7 +103,7 @@ Meteor.startup(() => {
metricsRes += '# Number of registered boards\n';
// Get number of registered boards
resCount = ReactiveCache.getBoards({ archived: false, type: 'board' }).length; // KPI 3
resCount = (await ReactiveCache.getBoards({ archived: false, type: 'board' })).length; // KPI 3
metricsRes += 'wekan_registeredboards ' + resCount + '\n';
resCount = 0;
@ -112,8 +112,8 @@ Meteor.startup(() => {
// Get number of registered boards by registered users
resCount =
ReactiveCache.getBoards({ archived: false, type: 'board' }).length /
ReactiveCache.getUsers({}).length; // KPI 4
(await ReactiveCache.getBoards({ archived: false, type: 'board' })).length /
(await ReactiveCache.getUsers({})).length; // KPI 4
metricsRes +=
'wekan_registeredboardsBysRegisteredUsers ' + resCount + '\n';
resCount = 0;
@ -122,11 +122,11 @@ Meteor.startup(() => {
metricsRes += '# Number of registered boards\n';
// Get board numbers with only one member
resCount = ReactiveCache.getBoards({
resCount = (await ReactiveCache.getBoards({
archived: false,
type: 'board',
members: { $size: 1 },
}).length; // KPI 5
})).length; // KPI 5
metricsRes +=
'wekan_registeredboardsWithOnlyOneMember ' + resCount + '\n';
resCount = 0;
@ -144,9 +144,9 @@ Meteor.startup(() => {
let dateWithXdaysAgo = new Date(
new Date() - xdays * 24 * 60 * 60 * 1000,
);
resCount = ReactiveCache.getUsers({
resCount = (await ReactiveCache.getUsers({
lastConnectionDate: { $gte: dateWithXdaysAgo },
}).length; // KPI 5
})).length; // KPI 5
metricsRes +=
'wekan_usersWithLastConnectionDated5DaysAgo ' + resCount + '\n';
resCount = 0;
@ -157,9 +157,9 @@ Meteor.startup(() => {
// Get number of users with last connection dated 10 days ago
xdays = 10;
dateWithXdaysAgo = new Date(new Date() - xdays * 24 * 60 * 60 * 1000);
resCount = ReactiveCache.getUsers({
resCount = (await ReactiveCache.getUsers({
lastConnectionDate: { $gte: dateWithXdaysAgo },
}).length; // KPI 5
})).length; // KPI 5
metricsRes +=
'wekan_usersWithLastConnectionDated10DaysAgo ' + resCount + '\n';
resCount = 0;
@ -170,9 +170,9 @@ Meteor.startup(() => {
// Get number of users with last connection dated 20 days ago
xdays = 20;
dateWithXdaysAgo = new Date(new Date() - xdays * 24 * 60 * 60 * 1000);
resCount = ReactiveCache.getUsers({
resCount = (await ReactiveCache.getUsers({
lastConnectionDate: { $gte: dateWithXdaysAgo },
}).length; // KPI 5
})).length; // KPI 5
metricsRes +=
'wekan_usersWithLastConnectionDated20DaysAgo ' + resCount + '\n';
resCount = 0;
@ -183,9 +183,9 @@ Meteor.startup(() => {
// Get number of users with last connection dated 20 days ago
xdays = 30;
dateWithXdaysAgo = new Date(new Date() - xdays * 24 * 60 * 60 * 1000);
resCount = ReactiveCache.getUsers({
resCount = (await ReactiveCache.getUsers({
lastConnectionDate: { $gte: dateWithXdaysAgo },
}).length; // KPI 5
})).length; // KPI 5
metricsRes +=
'wekan_usersWithLastConnectionDated30DaysAgo ' + resCount + '\n';
resCount = 0;

View file

@ -224,7 +224,7 @@ Settings.helpers({
});
Settings.allow({
update(userId) {
const user = ReactiveCache.getUser(userId);
const user = Meteor.users.findOne(userId);
return user && user.isAdmin;
},
});
@ -232,7 +232,7 @@ Settings.allow({
if (Meteor.isServer) {
Meteor.startup(async () => {
await Settings._collection.createIndexAsync({ modifiedAt: -1 });
const setting = ReactiveCache.getCurrentSetting();
const setting = await ReactiveCache.getCurrentSetting();
if (!setting) {
const now = new Date();
const domain = process.env.ROOT_URL.match(
@ -258,7 +258,7 @@ if (Meteor.isServer) {
}
if (isSandstorm) {
// At Sandstorm, Admin Panel has SMTP settings
const newSetting = ReactiveCache.getCurrentSetting();
const newSetting = await ReactiveCache.getCurrentSetting();
if (!process.env.MAIL_URL && newSetting.mailUrl())
process.env.MAIL_URL = newSetting.mailUrl();
Accounts.emailTemplates.from = process.env.MAIL_FROM
@ -312,15 +312,16 @@ if (Meteor.isServer) {
return config;
}
function sendInvitationEmail(_id) {
const icode = ReactiveCache.getInvitationCode(_id);
const author = ReactiveCache.getCurrentUser();
async function sendInvitationEmail(_id) {
const icode = await ReactiveCache.getInvitationCode(_id);
const author = await ReactiveCache.getCurrentUser();
try {
const fullName = ReactiveCache.getUser(icode.authorId)?.profile?.fullname || "";
const authorUser = await ReactiveCache.getUser(icode.authorId);
const fullName = authorUser?.profile?.fullname || "";
const params = {
email: icode.email,
inviter: fullName != "" ? fullName + " (" + ReactiveCache.getUser(icode.authorId).username + " )" : ReactiveCache.getUser(icode.authorId).username,
inviter: fullName != "" ? fullName + " (" + authorUser.username + " )" : authorUser.username,
user: icode.email.split('@')[0],
icode: icode.code,
url: FlowRouter.url('sign-up'),
@ -351,8 +352,8 @@ if (Meteor.isServer) {
}
}
function isNonAdminAllowedToSendMail(currentUser){
const currSett = ReactiveCache.getCurrentSetting();
async function isNonAdminAllowedToSendMail(currentUser){
const currSett = await ReactiveCache.getCurrentSetting();
let isAllowed = false;
if(currSett && currSett != undefined && currSett.disableRegistration && currSett.mailDomainName !== undefined && currSett.mailDomainName != ""){
for(let i = 0; i < currentUser.emails.length; i++) {
@ -389,20 +390,20 @@ if (Meteor.isServer) {
}
Meteor.methods({
sendInvitation(emails, boards) {
async sendInvitation(emails, boards) {
let rc = 0;
check(emails, [String]);
check(boards, [String]);
const user = ReactiveCache.getCurrentUser();
if (!user.isAdmin && !isNonAdminAllowedToSendMail(user)) {
const user = await ReactiveCache.getCurrentUser();
if (!user.isAdmin && !(await isNonAdminAllowedToSendMail(user))) {
rc = -1;
throw new Meteor.Error('not-allowed');
}
emails.forEach(email => {
for (const email of emails) {
if (email && SimpleSchema.RegEx.Email.test(email)) {
// Checks if the email is already link to an account.
const userExist = ReactiveCache.getUser({ email });
const userExist = await ReactiveCache.getUser({ email });
if (userExist) {
rc = -1;
throw new Meteor.Error(
@ -411,12 +412,12 @@ if (Meteor.isServer) {
);
}
// Checks if the email is already link to an invitation.
const invitation = ReactiveCache.getInvitationCode({ email });
const invitation = await ReactiveCache.getInvitationCode({ email });
if (invitation) {
InvitationCodes.update(invitation, {
$set: { boardsToBeInvited: boards },
});
sendInvitationEmail(invitation._id);
await sendInvitationEmail(invitation._id);
} else {
const code = getRandomNum(100000, 999999);
InvitationCodes.insert(
@ -427,9 +428,9 @@ if (Meteor.isServer) {
createdAt: new Date(),
authorId: Meteor.userId(),
},
function(err, _id) {
async function(err, _id) {
if (!err && _id) {
sendInvitationEmail(_id);
await sendInvitationEmail(_id);
} else {
rc = -1;
throw new Meteor.Error(
@ -441,15 +442,15 @@ if (Meteor.isServer) {
);
}
}
});
}
return rc;
},
sendSMTPTestEmail() {
async sendSMTPTestEmail() {
if (!Meteor.userId()) {
throw new Meteor.Error('invalid-user');
}
const user = ReactiveCache.getCurrentUser();
const user = await ReactiveCache.getCurrentUser();
if (!user.emails || !user.emails[0] || !user.emails[0].address) {
throw new Meteor.Error('email-invalid');
}
@ -499,8 +500,8 @@ if (Meteor.isServer) {
};
},
getCustomUI() {
const setting = ReactiveCache.getCurrentSetting();
async getCustomUI() {
const setting = await ReactiveCache.getCurrentSetting();
if (!setting.productName) {
return {
productName: '',
@ -512,8 +513,8 @@ if (Meteor.isServer) {
}
},
isDisableRegistration() {
const setting = ReactiveCache.getCurrentSetting();
async isDisableRegistration() {
const setting = await ReactiveCache.getCurrentSetting();
if (setting.disableRegistration === true) {
return true;
} else {
@ -521,8 +522,8 @@ if (Meteor.isServer) {
}
},
isDisableForgotPassword() {
const setting = ReactiveCache.getCurrentSetting();
async isDisableForgotPassword() {
const setting = await ReactiveCache.getCurrentSetting();
if (setting.disableForgotPassword === true) {
return true;
} else {

View file

@ -133,15 +133,15 @@ Swimlanes.attachSchema(
Swimlanes.allow({
insert(userId, doc) {
// ReadOnly and CommentOnly users cannot create swimlanes
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId));
},
update(userId, doc) {
// ReadOnly and CommentOnly users cannot edit swimlanes
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId));
},
remove(userId, doc) {
// ReadOnly and CommentOnly users cannot delete swimlanes
return allowIsBoardMemberWithWriteAccess(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId));
},
fetch: ['boardId'],
});
@ -152,7 +152,7 @@ Swimlanes.helpers({
const oldBoardId = this.boardId;
this.boardId = boardId;
delete this._id;
const _id = Swimlanes.insert(this);
const _id = await Swimlanes.insertAsync(this);
const query = {
swimlaneId: { $in: [oldId, ''] },
@ -163,7 +163,7 @@ Swimlanes.helpers({
}
// Copy all lists in swimlane
const lists = ReactiveCache.getLists(query);
const lists = await ReactiveCache.getLists(query);
for (const list of lists) {
list.type = 'list';
list.swimlaneId = oldId;
@ -175,8 +175,8 @@ Swimlanes.helpers({
},
async move(toBoardId) {
for (const list of this.lists()) {
const toList = ReactiveCache.getList({
for (const list of await this.lists()) {
const toList = await ReactiveCache.getList({
boardId: toBoardId,
title: list.title,
archived: false,
@ -196,7 +196,7 @@ Swimlanes.helpers({
});
}
const cards = ReactiveCache.getCards({
const cards = await ReactiveCache.getCards({
listId: list._id,
swimlaneId: this._id,
});
@ -212,7 +212,7 @@ Swimlanes.helpers({
});
// make sure there is a default swimlane
this.board().getDefaultSwimline();
(await this.board()).getDefaultSwimline();
},
cards() {
@ -331,7 +331,7 @@ Swimlanes.helpers({
async archive() {
if (this.isTemplateSwimlane()) {
for (const list of this.myLists()) {
for (const list of await this.myLists()) {
await list.archive();
}
}
@ -340,7 +340,7 @@ Swimlanes.helpers({
async restore() {
if (this.isTemplateSwimlane()) {
for (const list of this.myLists()) {
for (const list of await this.myLists()) {
await list.restore();
}
}
@ -352,23 +352,25 @@ Swimlanes.helpers({
},
});
Swimlanes.userArchivedSwimlanes = userId => {
return ReactiveCache.getSwimlanes({
boardId: { $in: Boards.userBoardIds(userId, null) },
Swimlanes.userArchivedSwimlanes = async userId => {
return await ReactiveCache.getSwimlanes({
boardId: { $in: await Boards.userBoardIds(userId, null) },
archived: true,
})
};
Swimlanes.userArchivedSwimlaneIds = () => {
return Swimlanes.userArchivedSwimlanes().map(swim => { return swim._id; });
Swimlanes.userArchivedSwimlaneIds = async () => {
const swimlanes = await Swimlanes.userArchivedSwimlanes();
return swimlanes.map(swim => { return swim._id; });
};
Swimlanes.archivedSwimlanes = () => {
return ReactiveCache.getSwimlanes({ archived: true });
Swimlanes.archivedSwimlanes = async () => {
return await ReactiveCache.getSwimlanes({ archived: true });
};
Swimlanes.archivedSwimlaneIds = () => {
return Swimlanes.archivedSwimlanes().map(swim => {
Swimlanes.archivedSwimlaneIds = async () => {
const swimlanes = await Swimlanes.archivedSwimlanes();
return swimlanes.map(swim => {
return swim._id;
});
};
@ -399,8 +401,8 @@ if (Meteor.isServer) {
}, 100);
});
Swimlanes.before.remove(function(userId, doc) {
const lists = ReactiveCache.getLists(
Swimlanes.before.remove(async function(userId, doc) {
const lists = await ReactiveCache.getLists(
{
boardId: doc.boardId,
swimlaneId: { $in: [doc._id, ''] },
@ -410,14 +412,14 @@ if (Meteor.isServer) {
);
if (lists.length < 2) {
lists.forEach(list => {
list.remove();
});
for (const list of lists) {
await list.remove();
}
} else {
Cards.remove({ swimlaneId: doc._id });
await Cards.removeAsync({ swimlaneId: doc._id });
}
Activities.insert({
await Activities.insertAsync({
userId,
type: 'swimlane',
activityType: 'removeSwimlane',
@ -476,14 +478,15 @@ if (Meteor.isServer) {
* @return_type [{_id: string,
* title: string}]
*/
JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes', function(req, res) {
JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes', async function(req, res) {
try {
const paramBoardId = req.params.boardId;
Authentication.checkBoardAccess(req.userId, paramBoardId);
const swimlanes = await ReactiveCache.getSwimlanes({ boardId: paramBoardId, archived: false });
JsonRoutes.sendResult(res, {
code: 200,
data: ReactiveCache.getSwimlanes({ boardId: paramBoardId, archived: false }).map(
data: swimlanes.map(
function(doc) {
return {
_id: doc._id,
@ -509,7 +512,7 @@ if (Meteor.isServer) {
* @param {string} swimlaneId the ID of the swimlane
* @return_type Swimlanes
*/
JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes/:swimlaneId', function(
JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes/:swimlaneId', async function(
req,
res,
) {
@ -520,7 +523,7 @@ if (Meteor.isServer) {
JsonRoutes.sendResult(res, {
code: 200,
data: ReactiveCache.getSwimlane({
data: await ReactiveCache.getSwimlane({
_id: paramSwimlaneId,
boardId: paramBoardId,
archived: false,
@ -543,13 +546,13 @@ if (Meteor.isServer) {
* @param {string} title the new title of the swimlane
* @return_type {_id: string}
*/
JsonRoutes.add('POST', '/api/boards/:boardId/swimlanes', function(req, res) {
JsonRoutes.add('POST', '/api/boards/:boardId/swimlanes', async function(req, res) {
try {
const paramBoardId = req.params.boardId;
Authentication.checkBoardWriteAccess(req.userId, paramBoardId);
const board = ReactiveCache.getBoard(paramBoardId);
const id = Swimlanes.insert({
const board = await ReactiveCache.getBoard(paramBoardId);
const id = await Swimlanes.insertAsync({
title: req.body.title,
boardId: paramBoardId,
sort: board.swimlanes().length,
@ -578,13 +581,13 @@ if (Meteor.isServer) {
* @param {string} title the new title of the swimlane
* @return_type {_id: string}
*/
JsonRoutes.add('PUT', '/api/boards/:boardId/swimlanes/:swimlaneId', function(req, res) {
JsonRoutes.add('PUT', '/api/boards/:boardId/swimlanes/:swimlaneId', async function(req, res) {
try {
const paramBoardId = req.params.boardId;
const paramSwimlaneId = req.params.swimlaneId;
Authentication.checkBoardWriteAccess(req.userId, paramBoardId);
const board = ReactiveCache.getBoard(paramBoardId);
const swimlane = ReactiveCache.getSwimlane({
const board = await ReactiveCache.getBoard(paramBoardId);
const swimlane = await ReactiveCache.getSwimlane({
_id: paramSwimlaneId,
boardId: paramBoardId,
});

View file

@ -46,7 +46,7 @@ TableVisibilityModeSettings.attachSchema(
TableVisibilityModeSettings.allow({
update(userId) {
const user = ReactiveCache.getUser(userId);
const user = Meteor.users.findOne(userId);
return user && user.isAdmin;
},
});

View file

@ -79,7 +79,7 @@ Team.attachSchema(
if (Meteor.isServer) {
Team.allow({
insert(userId, doc) {
const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser();
const user = Meteor.users.findOne(userId);
if (user?.isAdmin)
return true;
if (!user) {
@ -88,7 +88,7 @@ if (Meteor.isServer) {
return doc._id === userId;
},
update(userId, doc) {
const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser();
const user = Meteor.users.findOne(userId);
if (user?.isAdmin)
return true;
if (!user) {
@ -97,7 +97,7 @@ if (Meteor.isServer) {
return doc._id === userId;
},
remove(userId, doc) {
const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser();
const user = Meteor.users.findOne(userId);
if (user?.isAdmin)
return true;
if (!user) {
@ -109,21 +109,21 @@ if (Meteor.isServer) {
});
Meteor.methods({
setCreateTeam(
async setCreateTeam(
teamDisplayName,
teamDesc,
teamShortName,
teamWebsite,
teamIsActive,
) {
if (ReactiveCache.getCurrentUser()?.isAdmin) {
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
check(teamDisplayName, String);
check(teamDesc, String);
check(teamShortName, String);
check(teamWebsite, String);
check(teamIsActive, Boolean);
const nTeamNames = ReactiveCache.getTeams({ teamShortName }).length;
const nTeamNames = (await ReactiveCache.getTeams({ teamShortName })).length;
if (nTeamNames > 0) {
throw new Meteor.Error('teamname-already-taken');
} else {
@ -137,7 +137,7 @@ if (Meteor.isServer) {
}
}
},
setCreateTeamFromOidc(
async setCreateTeamFromOidc(
teamDisplayName,
teamDesc,
teamShortName,
@ -149,7 +149,7 @@ if (Meteor.isServer) {
check(teamShortName, String);
check(teamWebsite, String);
check(teamIsActive, Boolean);
const nTeamNames = ReactiveCache.getTeams({ teamShortName }).length;
const nTeamNames = (await ReactiveCache.getTeams({ teamShortName })).length;
if (nTeamNames > 0) {
throw new Meteor.Error('teamname-already-taken');
} else {
@ -162,19 +162,19 @@ if (Meteor.isServer) {
});
}
},
setTeamDisplayName(team, teamDisplayName) {
if (ReactiveCache.getCurrentUser()?.isAdmin) {
async setTeamDisplayName(team, teamDisplayName) {
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
check(team, Object);
check(teamDisplayName, String);
Team.update(team, {
$set: { teamDisplayName: teamDisplayName },
});
Meteor.call('setUsersTeamsTeamDisplayName', team._id, teamDisplayName);
await Meteor.callAsync('setUsersTeamsTeamDisplayName', team._id, teamDisplayName);
}
},
setTeamDesc(team, teamDesc) {
if (ReactiveCache.getCurrentUser()?.isAdmin) {
async setTeamDesc(team, teamDesc) {
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
check(team, Object);
check(teamDesc, String);
Team.update(team, {
@ -183,8 +183,8 @@ if (Meteor.isServer) {
}
},
setTeamShortName(team, teamShortName) {
if (ReactiveCache.getCurrentUser()?.isAdmin) {
async setTeamShortName(team, teamShortName) {
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
check(team, Object);
check(teamShortName, String);
Team.update(team, {
@ -193,8 +193,8 @@ if (Meteor.isServer) {
}
},
setTeamIsActive(team, teamIsActive) {
if (ReactiveCache.getCurrentUser()?.isAdmin) {
async setTeamIsActive(team, teamIsActive) {
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
check(team, Object);
check(teamIsActive, Boolean);
Team.update(team, {
@ -202,7 +202,7 @@ if (Meteor.isServer) {
});
}
},
setTeamAllFieldsFromOidc(
async setTeamAllFieldsFromOidc(
team,
teamDisplayName,
teamDesc,
@ -225,9 +225,9 @@ if (Meteor.isServer) {
teamIsActive: teamIsActive,
},
});
Meteor.call('setUsersTeamsTeamDisplayName', team._id, teamDisplayName);
await Meteor.callAsync('setUsersTeamsTeamDisplayName', team._id, teamDisplayName);
},
setTeamAllFields(
async setTeamAllFields(
team,
teamDisplayName,
teamDesc,
@ -235,7 +235,7 @@ if (Meteor.isServer) {
teamWebsite,
teamIsActive,
) {
if (ReactiveCache.getCurrentUser()?.isAdmin) {
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
check(team, Object);
check(teamDisplayName, String);
check(teamDesc, String);
@ -251,7 +251,7 @@ if (Meteor.isServer) {
teamIsActive: teamIsActive,
},
});
Meteor.call('setUsersTeamsTeamDisplayName', team._id, teamDisplayName);
await Meteor.callAsync('setUsersTeamsTeamDisplayName', team._id, teamDisplayName);
}
},
});

View file

@ -59,7 +59,7 @@ Translation.attachSchema(
if (Meteor.isServer) {
Translation.allow({
insert(userId, doc) {
const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser();
const user = Meteor.users.findOne(userId);
if (user?.isAdmin)
return true;
if (!user) {
@ -68,7 +68,7 @@ if (Meteor.isServer) {
return doc._id === userId;
},
update(userId, doc) {
const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser();
const user = Meteor.users.findOne(userId);
if (user?.isAdmin)
return true;
if (!user) {
@ -77,7 +77,7 @@ if (Meteor.isServer) {
return doc._id === userId;
},
remove(userId, doc) {
const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser();
const user = Meteor.users.findOne(userId);
if (user?.isAdmin)
return true;
if (!user) {
@ -89,7 +89,7 @@ if (Meteor.isServer) {
});
Meteor.methods({
setCreateTranslation(
async setCreateTranslation(
language,
text,
translationText,
@ -98,11 +98,11 @@ if (Meteor.isServer) {
check(text, String);
check(translationText, String);
if (!ReactiveCache.getCurrentUser()?.isAdmin) {
if (!(await ReactiveCache.getCurrentUser())?.isAdmin) {
throw new Meteor.Error('not-authorized');
}
const nTexts = ReactiveCache.getTranslations({ language, text }).length;
const nTexts = (await ReactiveCache.getTranslations({ language, text })).length;
if (nTexts > 0) {
throw new Meteor.Error('text-already-taken');
} else {
@ -113,11 +113,11 @@ if (Meteor.isServer) {
});
}
},
setTranslationText(translation, translationText) {
async setTranslationText(translation, translationText) {
check(translation, Object);
check(translationText, String);
if (!ReactiveCache.getCurrentUser()?.isAdmin) {
if (!(await ReactiveCache.getCurrentUser())?.isAdmin) {
throw new Meteor.Error('not-authorized');
}
@ -125,10 +125,10 @@ if (Meteor.isServer) {
$set: { translationText: translationText },
});
},
deleteTranslation(translationId) {
async deleteTranslation(translationId) {
check(translationId, String);
if (!ReactiveCache.getCurrentUser()?.isAdmin) {
if (!(await ReactiveCache.getCurrentUser())?.isAdmin) {
throw new Meteor.Error('not-authorized');
}

View file

@ -181,7 +181,7 @@ export class TrelloCreator {
}
// You must call parseActions before calling this one.
createBoardAndLabels(trelloBoard) {
async createBoardAndLabels(trelloBoard) {
let color = 'blue';
if (this.getColor(trelloBoard.prefs.background) !== undefined) {
color = this.getColor(trelloBoard.prefs.background);
@ -207,7 +207,7 @@ export class TrelloCreator {
permission: this.getPermission(trelloBoard.prefs.permissionLevel),
slug: getSlug(trelloBoard.name) || 'board',
stars: 0,
title: Boards.uniqueTitle(trelloBoard.name),
title: await Boards.uniqueTitle(trelloBoard.name),
};
// now add other members
if (trelloBoard.memberships) {
@ -775,11 +775,11 @@ export class TrelloCreator {
Meteor.settings.public &&
Meteor.settings.public.sandstorm;
if (isSandstorm && currentBoardId) {
const currentBoard = ReactiveCache.getBoard(currentBoardId);
const currentBoard = await ReactiveCache.getBoard(currentBoardId);
await currentBoard.archive();
}
this.parseActions(board.actions);
const boardId = this.createBoardAndLabels(board);
const boardId = await this.createBoardAndLabels(board);
this.createLists(board.lists, boardId);
this.createSwimlanes(boardId);
this.createCards(board.cards, boardId);

View file

@ -15,13 +15,13 @@ Triggers.before.update((userId, doc, fieldNames, modifier) => {
Triggers.allow({
insert(userId, doc) {
return allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
},
update(userId, doc) {
return allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
},
remove(userId, doc) {
return allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId));
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
},
});

View file

@ -174,17 +174,17 @@ UserPositionHistory.helpers({
/**
* Can this change be undone?
*/
canUndo() {
async canUndo() {
// Can undo if the entity still exists
switch (this.entityType) {
case 'card':
return !!ReactiveCache.getCard(this.entityId);
return !!(await ReactiveCache.getCard(this.entityId));
case 'list':
return !!ReactiveCache.getList(this.entityId);
return !!(await ReactiveCache.getList(this.entityId));
case 'swimlane':
return !!ReactiveCache.getSwimlane(this.entityId);
return !!(await ReactiveCache.getSwimlane(this.entityId));
case 'checklist':
return !!ReactiveCache.getChecklist(this.entityId);
return !!(await ReactiveCache.getChecklist(this.entityId));
case 'checklistItem':
return !!ChecklistItems.findOne(this.entityId);
default:
@ -195,8 +195,8 @@ UserPositionHistory.helpers({
/**
* Undo this change
*/
undo() {
if (!this.canUndo()) {
async undo() {
if (!(await this.canUndo())) {
throw new Meteor.Error('cannot-undo', 'Entity no longer exists');
}
@ -204,7 +204,7 @@ UserPositionHistory.helpers({
switch (this.entityType) {
case 'card': {
const card = ReactiveCache.getCard(this.entityId);
const card = await ReactiveCache.getCard(this.entityId);
if (card) {
// Restore previous position
const boardId = this.previousBoardId || card.boardId;
@ -224,7 +224,7 @@ UserPositionHistory.helpers({
break;
}
case 'list': {
const list = ReactiveCache.getList(this.entityId);
const list = await ReactiveCache.getList(this.entityId);
if (list) {
const sort = this.previousSort !== undefined ? this.previousSort : list.sort;
const swimlaneId = this.previousSwimlaneId || list.swimlaneId;
@ -239,7 +239,7 @@ UserPositionHistory.helpers({
break;
}
case 'swimlane': {
const swimlane = ReactiveCache.getSwimlane(this.entityId);
const swimlane = await ReactiveCache.getSwimlane(this.entityId);
if (swimlane) {
const sort = this.previousSort !== undefined ? this.previousSort : swimlane.sort;
@ -252,7 +252,7 @@ UserPositionHistory.helpers({
break;
}
case 'checklist': {
const checklist = ReactiveCache.getChecklist(this.entityId);
const checklist = await ReactiveCache.getChecklist(this.entityId);
if (checklist) {
const sort = this.previousSort !== undefined ? this.previousSort : checklist.sort;
@ -411,7 +411,7 @@ Meteor.methods({
});
},
'userPositionHistory.undo'(historyId) {
async 'userPositionHistory.undo'(historyId) {
check(historyId, String);
if (!this.userId) {
@ -423,7 +423,7 @@ Meteor.methods({
throw new Meteor.Error('not-found', 'History entry not found');
}
return history.undo();
return await history.undo();
},
'userPositionHistory.getRecent'(boardId, limit = 50) {
@ -453,7 +453,7 @@ Meteor.methods({
).fetch();
},
'userPositionHistory.restoreToCheckpoint'(checkpointId) {
async 'userPositionHistory.restoreToCheckpoint'(checkpointId) {
check(checkpointId, String);
if (!this.userId) {
@ -482,16 +482,16 @@ Meteor.methods({
).fetch();
let undoneCount = 0;
changesToUndo.forEach(change => {
for (const change of changesToUndo) {
try {
if (change.canUndo()) {
change.undo();
if (await change.canUndo()) {
await change.undo();
undoneCount++;
}
} catch (e) {
console.warn('Failed to undo change:', change._id, e);
}
});
};
return { undoneCount, totalChanges: changesToUndo.length };
},

View file

@ -1798,7 +1798,7 @@ Users.helpers({
Meteor.methods({
// Secure user removal method with proper authorization checks
removeUser(targetUserId) {
async removeUser(targetUserId) {
check(targetUserId, String);
const currentUserId = Meteor.userId();
@ -1806,12 +1806,12 @@ Meteor.methods({
throw new Meteor.Error('not-authorized', 'User must be logged in');
}
const currentUser = ReactiveCache.getUser(currentUserId);
const currentUser = await ReactiveCache.getUser(currentUserId);
if (!currentUser) {
throw new Meteor.Error('not-authorized', 'Current user not found');
}
const targetUser = ReactiveCache.getUser(targetUserId);
const targetUser = await ReactiveCache.getUser(targetUserId);
if (!targetUser) {
throw new Meteor.Error('user-not-found', 'Target user not found');
}
@ -1829,9 +1829,9 @@ Meteor.methods({
}
// Check if target user is the last admin
const adminsNumber = ReactiveCache.getUsers({
const adminsNumber = (await ReactiveCache.getUsers({
isAdmin: true,
}).length;
})).length;
if (adminsNumber === 1 && targetUser.isAdmin) {
throw new Meteor.Error('not-authorized', 'Cannot delete the last administrator');
@ -1841,7 +1841,7 @@ Meteor.methods({
Users.remove(targetUserId);
return { success: true, message: 'User deleted successfully' };
},
editUser(targetUserId, updateData) {
async editUser(targetUserId, updateData) {
check(targetUserId, String);
check(updateData, Object);
@ -1850,7 +1850,7 @@ Meteor.methods({
throw new Meteor.Error('not-authorized', 'User must be logged in');
}
const currentUser = ReactiveCache.getUser(currentUserId);
const currentUser = await ReactiveCache.getUser(currentUserId);
if (!currentUser) {
throw new Meteor.Error('not-authorized', 'Current user not found');
}
@ -1860,7 +1860,7 @@ Meteor.methods({
throw new Meteor.Error('not-authorized', 'Only administrators can edit other users');
}
const targetUser = ReactiveCache.getUser(targetUserId);
const targetUser = await ReactiveCache.getUser(targetUserId);
if (!targetUser) {
throw new Meteor.Error('user-not-found', 'Target user not found');
}
@ -1894,9 +1894,9 @@ Meteor.methods({
Users.update(targetUserId, { $set: updateObject });
},
setListSortBy(value) {
async setListSortBy(value) {
check(value, String);
ReactiveCache.getCurrentUser().setListSortBy(value);
(await ReactiveCache.getCurrentUser()).setListSortBy(value);
},
setAvatarUrl(avatarUrl) {
check(avatarUrl, String);
@ -1943,8 +1943,8 @@ Meteor.methods({
Users.update(this.userId, { $set: { 'profile.GreyIcons': newValue } });
return newValue;
},
toggleDesktopDragHandles() {
const user = ReactiveCache.getCurrentUser();
async toggleDesktopDragHandles() {
const user = await ReactiveCache.getCurrentUser();
user.toggleDesktopHandles(user.hasShowDesktopDragHandles());
},
// Spaces: create a new space under parentId (or root when null)
@ -2015,16 +2015,16 @@ Meteor.methods({
});
return true;
},
toggleHideCheckedItems() {
const user = ReactiveCache.getCurrentUser();
async toggleHideCheckedItems() {
const user = await ReactiveCache.getCurrentUser();
user.toggleHideCheckedItems();
},
toggleCustomFieldsGrid() {
const user = ReactiveCache.getCurrentUser();
async toggleCustomFieldsGrid() {
const user = await ReactiveCache.getCurrentUser();
user.toggleFieldsGrid(user.hasCustomFieldsGrid());
},
toggleCardMaximized() {
const user = ReactiveCache.getCurrentUser();
async toggleCardMaximized() {
const user = await ReactiveCache.getCurrentUser();
user.toggleCardMaximized(user.hasCardMaximized());
},
setCardCollapsed(value) {
@ -2032,32 +2032,32 @@ Meteor.methods({
if (!this.userId) throw new Meteor.Error('not-logged-in');
Users.update(this.userId, { $set: { 'profile.cardCollapsed': value } });
},
toggleMinicardLabelText() {
const user = ReactiveCache.getCurrentUser();
async toggleMinicardLabelText() {
const user = await ReactiveCache.getCurrentUser();
user.toggleLabelText(user.hasHiddenMinicardLabelText());
},
toggleRescueCardDescription() {
const user = ReactiveCache.getCurrentUser();
async toggleRescueCardDescription() {
const user = await ReactiveCache.getCurrentUser();
user.toggleRescueCardDescription(user.hasRescuedCardDescription());
},
changeLimitToShowCardsCount(limit) {
async changeLimitToShowCardsCount(limit) {
check(limit, Number);
ReactiveCache.getCurrentUser().setShowCardsCountAt(limit);
(await ReactiveCache.getCurrentUser()).setShowCardsCountAt(limit);
},
changeStartDayOfWeek(startDay) {
async changeStartDayOfWeek(startDay) {
check(startDay, Number);
ReactiveCache.getCurrentUser().setStartDayOfWeek(startDay);
(await ReactiveCache.getCurrentUser()).setStartDayOfWeek(startDay);
},
changeDateFormat(dateFormat) {
async changeDateFormat(dateFormat) {
check(dateFormat, String);
ReactiveCache.getCurrentUser().setDateFormat(dateFormat);
(await ReactiveCache.getCurrentUser()).setDateFormat(dateFormat);
},
applyListWidth(boardId, listId, width, constraint) {
async applyListWidth(boardId, listId, width, constraint) {
check(boardId, String);
check(listId, String);
check(width, Number);
check(constraint, Number);
const user = ReactiveCache.getCurrentUser();
const user = await ReactiveCache.getCurrentUser();
user.setListWidth(boardId, listId, width);
user.setListConstraint(boardId, listId, constraint);
},
@ -2081,11 +2081,11 @@ Meteor.methods({
},
});
},
applySwimlaneHeight(boardId, swimlaneId, height) {
async applySwimlaneHeight(boardId, swimlaneId, height) {
check(boardId, String);
check(swimlaneId, String);
check(height, Number);
const user = ReactiveCache.getCurrentUser();
const user = await ReactiveCache.getCurrentUser();
user.setSwimlaneHeight(boardId, swimlaneId, height);
},
@ -2110,42 +2110,42 @@ Meteor.methods({
});
},
applySwimlaneHeightToStorage(boardId, swimlaneId, height) {
async applySwimlaneHeightToStorage(boardId, swimlaneId, height) {
check(boardId, String);
check(swimlaneId, String);
check(height, Number);
const user = ReactiveCache.getCurrentUser();
const user = await ReactiveCache.getCurrentUser();
if (user) {
user.setSwimlaneHeightToStorage(boardId, swimlaneId, height);
}
// For non-logged-in users, the client-side code will handle localStorage
},
applyListWidthToStorage(boardId, listId, width, constraint) {
async applyListWidthToStorage(boardId, listId, width, constraint) {
check(boardId, String);
check(listId, String);
check(width, Number);
check(constraint, Number);
const user = ReactiveCache.getCurrentUser();
const user = await ReactiveCache.getCurrentUser();
if (user) {
user.setListWidthToStorage(boardId, listId, width);
user.setListConstraintToStorage(boardId, listId, constraint);
}
// For non-logged-in users, the client-side code will handle localStorage
},
setZoomLevel(level) {
async setZoomLevel(level) {
check(level, Number);
const user = ReactiveCache.getCurrentUser();
const user = await ReactiveCache.getCurrentUser();
user.setZoomLevel(level);
},
setMobileMode(enabled) {
async setMobileMode(enabled) {
check(enabled, Boolean);
const user = ReactiveCache.getCurrentUser();
const user = await ReactiveCache.getCurrentUser();
user.setMobileMode(enabled);
},
setBoardView(view) {
async setBoardView(view) {
check(view, String);
const user = ReactiveCache.getCurrentUser();
const user = await ReactiveCache.getCurrentUser();
if (!user) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
@ -2155,7 +2155,7 @@ Meteor.methods({
if (Meteor.isServer) {
Meteor.methods({
setCreateUser(
async setCreateUser(
fullname,
username,
initials,
@ -2185,13 +2185,13 @@ if (Meteor.isServer) {
initials.includes('/')) {
return false;
}
if (ReactiveCache.getCurrentUser()?.isAdmin) {
const nUsersWithUsername = ReactiveCache.getUsers({
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
const nUsersWithUsername = (await ReactiveCache.getUsers({
username,
}).length;
const nUsersWithEmail = ReactiveCache.getUsers({
})).length;
const nUsersWithEmail = (await ReactiveCache.getUsers({
email,
}).length;
})).length;
if (nUsersWithUsername > 0) {
throw new Meteor.Error('username-already-taken');
} else if (nUsersWithEmail > 0) {
@ -2206,8 +2206,8 @@ if (Meteor.isServer) {
from: 'admin',
});
const user =
ReactiveCache.getUser(username) ||
ReactiveCache.getUser({ username });
await ReactiveCache.getUser(username) ||
await ReactiveCache.getUser({ username });
if (user) {
Users.update(user._id, {
$set: {
@ -2222,7 +2222,7 @@ if (Meteor.isServer) {
}
}
},
setUsername(username, userId) {
async setUsername(username, userId) {
check(username, String);
check(userId, String);
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
@ -2231,10 +2231,10 @@ if (Meteor.isServer) {
userId.includes('/')) {
return false;
}
if (ReactiveCache.getCurrentUser()?.isAdmin) {
const nUsersWithUsername = ReactiveCache.getUsers({
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
const nUsersWithUsername = (await ReactiveCache.getUsers({
username,
}).length;
})).length;
if (nUsersWithUsername > 0) {
throw new Meteor.Error('username-already-taken');
} else {
@ -2246,7 +2246,7 @@ if (Meteor.isServer) {
}
}
},
setEmail(email, userId) {
async setEmail(email, userId) {
check(email, String);
check(username, String);
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
@ -2255,11 +2255,11 @@ if (Meteor.isServer) {
email.includes('/')) {
return false;
}
if (ReactiveCache.getCurrentUser()?.isAdmin) {
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
if (Array.isArray(email)) {
email = email.shift();
}
const existingUser = ReactiveCache.getUser(
const existingUser = await ReactiveCache.getUser(
{
'emails.address': email,
},
@ -2285,7 +2285,7 @@ if (Meteor.isServer) {
}
}
},
setUsernameAndEmail(username, email, userId) {
async setUsernameAndEmail(username, email, userId) {
check(username, String);
check(email, String);
check(userId, String);
@ -2296,22 +2296,22 @@ if (Meteor.isServer) {
userId.includes('/')) {
return false;
}
if (ReactiveCache.getCurrentUser()?.isAdmin) {
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
if (Array.isArray(email)) {
email = email.shift();
}
Meteor.call('setUsername', username, userId);
Meteor.call('setEmail', email, userId);
await Meteor.callAsync('setUsername', username, userId);
await Meteor.callAsync('setEmail', email, userId);
}
},
setPassword(newPassword, userId) {
async setPassword(newPassword, userId) {
check(userId, String);
check(newPassword, String);
if (ReactiveCache.getCurrentUser()?.isAdmin) {
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
Accounts.setPassword(userId, newPassword);
}
},
setEmailVerified(email, verified, userId) {
async setEmailVerified(email, verified, userId) {
check(email, String);
check(verified, Boolean);
check(userId, String);
@ -2321,7 +2321,7 @@ if (Meteor.isServer) {
userId.includes('/')) {
return false;
}
if (ReactiveCache.getCurrentUser()?.isAdmin) {
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
Users.update(userId, {
$set: {
emails: [
@ -2334,7 +2334,7 @@ if (Meteor.isServer) {
});
}
},
setInitials(initials, userId) {
async setInitials(initials, userId) {
check(initials, String);
check(userId, String);
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
@ -2343,7 +2343,7 @@ if (Meteor.isServer) {
userId.includes('/')) {
return false;
}
if (ReactiveCache.getCurrentUser()?.isAdmin) {
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
Users.update(userId, {
$set: {
'profile.initials': initials,
@ -2352,7 +2352,7 @@ if (Meteor.isServer) {
}
},
// we accept userId, username, email
inviteUserToBoard(username, boardId) {
async inviteUserToBoard(username, boardId) {
check(username, String);
check(boardId, String);
// Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176
@ -2361,8 +2361,8 @@ if (Meteor.isServer) {
boardId.includes('/')) {
return false;
}
const inviter = ReactiveCache.getCurrentUser();
const board = ReactiveCache.getBoard(boardId);
const inviter = await ReactiveCache.getCurrentUser();
const board = await ReactiveCache.getBoard(boardId);
const member = _.find(board.members, function(member) { return member.userId === inviter._id; });
if (!member) throw new Meteor.Error('error-board-notAMember');
const allowInvite = member.isActive;
@ -2375,7 +2375,7 @@ if (Meteor.isServer) {
const posAt = username.indexOf('@');
let user = null;
if (posAt >= 0) {
user = ReactiveCache.getUser({
user = await ReactiveCache.getUser({
emails: {
$elemMatch: {
address: username,
@ -2384,15 +2384,15 @@ if (Meteor.isServer) {
});
} else {
user =
ReactiveCache.getUser(username) ||
ReactiveCache.getUser({ username });
await ReactiveCache.getUser(username) ||
await ReactiveCache.getUser({ username });
}
if (user) {
if (user._id === inviter._id)
throw new Meteor.Error('error-user-notAllowSelf');
} else {
if (posAt <= 0) throw new Meteor.Error('error-user-doesNotExist');
if (ReactiveCache.getCurrentSetting().disableRegistration) {
if ((await ReactiveCache.getCurrentSetting()).disableRegistration) {
throw new Meteor.Error('error-user-notCreated');
}
// Set in lowercase email before creating account
@ -2418,7 +2418,7 @@ if (Meteor.isServer) {
});
}
Accounts.sendEnrollmentEmail(newUserId);
user = ReactiveCache.getUser(newUserId);
user = await ReactiveCache.getUser(newUserId);
}
const memberIndex = board.members.findIndex(m => m.userId === user._id);
@ -2431,7 +2431,7 @@ if (Meteor.isServer) {
//Check if there is a subtasks board
if (board.subtasksDefaultBoardId) {
const subBoard = ReactiveCache.getBoard(board.subtasksDefaultBoardId);
const subBoard = await ReactiveCache.getBoard(board.subtasksDefaultBoardId);
//If there is, also add user to that board
if (subBoard) {
const subMemberIndex = subBoard.members.findIndex(m => m.userId === user._id);
@ -2495,35 +2495,35 @@ if (Meteor.isServer) {
email: user.emails[0].address,
};
},
impersonate(userId) {
async impersonate(userId) {
check(userId, String);
if (!ReactiveCache.getUser(userId))
if (!(await ReactiveCache.getUser(userId)))
throw new Meteor.Error(404, 'User not found');
if (!ReactiveCache.getCurrentUser().isAdmin)
if (!(await ReactiveCache.getCurrentUser()).isAdmin)
throw new Meteor.Error(403, 'Permission denied');
ImpersonatedUsers.insert({
adminId: ReactiveCache.getCurrentUser()._id,
adminId: (await ReactiveCache.getCurrentUser())._id,
userId: userId,
reason: 'clickedImpersonate',
});
this.setUserId(userId);
},
isImpersonated(userId) {
async isImpersonated(userId) {
check(userId, String);
const isImpersonated = ReactiveCache.getImpersonatedUser({ userId: userId });
const isImpersonated = await ReactiveCache.getImpersonatedUser({ userId: userId });
return isImpersonated;
},
setUsersTeamsTeamDisplayName(teamId, teamDisplayName) {
async setUsersTeamsTeamDisplayName(teamId, teamDisplayName) {
check(teamId, String);
check(teamDisplayName, String);
if (ReactiveCache.getCurrentUser()?.isAdmin) {
ReactiveCache.getUsers({
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
for (const user of await ReactiveCache.getUsers({
teams: {
$elemMatch: { teamId: teamId },
},
}).forEach((user) => {
})) {
Users.update(
{
_id: user._id,
@ -2537,18 +2537,18 @@ if (Meteor.isServer) {
},
},
);
});
}
}
},
setUsersOrgsOrgDisplayName(orgId, orgDisplayName) {
async setUsersOrgsOrgDisplayName(orgId, orgDisplayName) {
check(orgId, String);
check(orgDisplayName, String);
if (ReactiveCache.getCurrentUser()?.isAdmin) {
ReactiveCache.getUsers({
if ((await ReactiveCache.getCurrentUser())?.isAdmin) {
for (const user of await ReactiveCache.getUsers({
orgs: {
$elemMatch: { orgId: orgId },
},
}).forEach((user) => {
})) {
Users.update(
{
_id: user._id,
@ -2562,12 +2562,12 @@ if (Meteor.isServer) {
},
},
);
});
}
}
},
});
Accounts.onCreateUser((options, user) => {
const userCount = ReactiveCache.getUsers({}, {}, true).count();
Accounts.onCreateUser(async (options, user) => {
const userCount = (await ReactiveCache.getUsers({}, {}, true)).count();
user.isAdmin = userCount === 0;
if (user.services.oidc) {
@ -2607,7 +2607,7 @@ if (Meteor.isServer) {
user.authenticationMethod = 'oauth2';
// see if any existing user has this email address or username, otherwise create new
const existingUser = ReactiveCache.getUser({
const existingUser = await ReactiveCache.getUser({
$or: [
{
'emails.address': email,
@ -2641,7 +2641,7 @@ if (Meteor.isServer) {
return user;
}
const disableRegistration = ReactiveCache.getCurrentSetting().disableRegistration;
const disableRegistration = (await ReactiveCache.getCurrentSetting()).disableRegistration;
// If this is the first Authentication by the ldap and self registration disabled
if (disableRegistration && options && options.ldap) {
user.authenticationMethod = 'ldap';
@ -2659,7 +2659,7 @@ if (Meteor.isServer) {
'The invitation code is required',
);
}
const invitationCode = ReactiveCache.getInvitationCode({
const invitationCode = await ReactiveCache.getInvitationCode({
code: options.profile.invitationcode,
email: options.email,
valid: true,
@ -2702,8 +2702,8 @@ const addCronJob = _.debounce(
SyncedCron.add({
name: 'notification_cleanup',
schedule: (parser) => parser.text('every 1 days'),
job: () => {
for (const user of ReactiveCache.getUsers()) {
job: async () => {
for (const user of await ReactiveCache.getUsers()) {
if (!user.profile || !user.profile.notifications) continue;
for (const notification of user.profile.notifications) {
if (notification.read) {
@ -2938,9 +2938,9 @@ if (Meteor.isServer) {
});
}
Users.after.insert((userId, doc) => {
Users.after.insert(async (userId, doc) => {
// HACK
doc = ReactiveCache.getUser(doc._id);
doc = await ReactiveCache.getUser(doc._id);
if (doc.createdThroughApi) {
// The admin user should be able to create a user despite disabling registration because
// it is two different things (registration and creation).
@ -2957,19 +2957,19 @@ if (Meteor.isServer) {
}
//invite user to corresponding boards
const disableRegistration = ReactiveCache.getCurrentSetting().disableRegistration;
const disableRegistration = (await ReactiveCache.getCurrentSetting()).disableRegistration;
// If ldap, bypass the inviation code if the self registration isn't allowed.
// TODO : pay attention if ldap field in the user model change to another content ex : ldap field to connection_type
if (doc.authenticationMethod !== 'ldap' && disableRegistration) {
let invitationCode = null;
if (doc.authenticationMethod.toLowerCase() == 'oauth2') {
// OIDC authentication mode
invitationCode = ReactiveCache.getInvitationCode({
invitationCode = await ReactiveCache.getInvitationCode({
email: doc.emails[0].address.toLowerCase(),
valid: true,
});
} else {
invitationCode = ReactiveCache.getInvitationCode({
invitationCode = await ReactiveCache.getInvitationCode({
code: doc.profile.icode,
valid: true,
});
@ -2977,15 +2977,15 @@ if (Meteor.isServer) {
if (!invitationCode) {
throw new Meteor.Error('error-invitation-code-not-exist');
} else {
invitationCode.boardsToBeInvited.forEach((boardId) => {
const board = ReactiveCache.getBoard(boardId);
for (const boardId of invitationCode.boardsToBeInvited) {
const board = await ReactiveCache.getBoard(boardId);
const memberIndex = board.members.findIndex(m => m.userId === doc._id);
if (memberIndex >= 0) {
Boards.update(boardId, { $set: { [`members.${memberIndex}.isActive`]: true } });
} else {
Boards.update(boardId, { $push: { members: { userId: doc._id, isAdmin: false, isActive: true, isNoComments: false, isCommentOnly: false, isWorker: false, isNormalAssignedOnly: false, isCommentAssignedOnly: false, isReadOnly: false, isReadAssignedOnly: false } } });
}
});
}
if (!doc.profile) {
doc.profile = {};
}
@ -3026,16 +3026,16 @@ if (Meteor.isServer) {
* @summary returns the current user
* @return_type Users
*/
JsonRoutes.add('GET', '/api/user', function (req, res) {
JsonRoutes.add('GET', '/api/user', async function (req, res) {
try {
Authentication.checkLoggedIn(req.userId);
const data = ReactiveCache.getUser({
const data = await ReactiveCache.getUser({
_id: req.userId,
});
delete data.services;
// get all boards where the user is member of
let boards = ReactiveCache.getBoards(
let boards = await ReactiveCache.getBoards(
{
type: 'board',
'members.userId': req.userId,
@ -3108,22 +3108,22 @@ if (Meteor.isServer) {
* @param {string} userId the user ID or username
* @return_type Users
*/
JsonRoutes.add('GET', '/api/users/:userId', function (req, res) {
JsonRoutes.add('GET', '/api/users/:userId', async function (req, res) {
try {
Authentication.checkUserId(req.userId);
let id = req.params.userId;
let user = ReactiveCache.getUser({
let user = await ReactiveCache.getUser({
_id: id,
});
if (!user) {
user = ReactiveCache.getUser({
user = await ReactiveCache.getUser({
username: id,
});
id = user._id;
}
// get all boards where the user is member of
let boards = ReactiveCache.getBoards(
let boards = await ReactiveCache.getBoards(
{
type: 'board',
'members.userId': id,
@ -3177,12 +3177,12 @@ if (Meteor.isServer) {
Authentication.checkUserId(req.userId);
const id = req.params.userId;
const action = req.body.action;
let data = ReactiveCache.getUser({
let data = await ReactiveCache.getUser({
_id: id,
});
if (data !== undefined) {
if (action === 'takeOwnership') {
const boards = ReactiveCache.getBoards(
const boards = await ReactiveCache.getBoards(
{
'members.userId': id,
'members.isAdmin': true,
@ -3229,7 +3229,7 @@ if (Meteor.isServer) {
},
);
}
data = ReactiveCache.getUser(id);
data = await ReactiveCache.getUser(id);
}
}
JsonRoutes.sendResult(res, {
@ -3272,19 +3272,19 @@ if (Meteor.isServer) {
JsonRoutes.add(
'POST',
'/api/boards/:boardId/members/:userId/add',
function (req, res) {
async function (req, res) {
try {
Authentication.checkUserId(req.userId);
const userId = req.params.userId;
const boardId = req.params.boardId;
const action = req.body.action;
const { isAdmin, isNoComments, isCommentOnly, isWorker, isNormalAssignedOnly, isCommentAssignedOnly, isReadOnly, isReadAssignedOnly } = req.body;
let data = ReactiveCache.getUser(userId);
let data = await ReactiveCache.getUser(userId);
if (data !== undefined) {
if (action === 'add') {
data = ReactiveCache.getBoards({
data = (await ReactiveCache.getBoards({
_id: boardId,
}).map(function (board) {
})).map(function (board) {
const hasMember = board.members.some(m => m.userId === userId && m.isActive);
if (!hasMember) {
const memberIndex = board.members.findIndex(m => m.userId === userId);
@ -3347,18 +3347,18 @@ if (Meteor.isServer) {
JsonRoutes.add(
'POST',
'/api/boards/:boardId/members/:userId/remove',
function (req, res) {
async function (req, res) {
try {
Authentication.checkUserId(req.userId);
const userId = req.params.userId;
const boardId = req.params.boardId;
const action = req.body.action;
let data = ReactiveCache.getUser(userId);
let data = await ReactiveCache.getUser(userId);
if (data !== undefined) {
if (action === 'remove') {
data = ReactiveCache.getBoards({
data = (await ReactiveCache.getBoards({
_id: boardId,
}).map(function (board) {
})).map(function (board) {
const hasMember = board.members.some(m => m.userId === userId && m.isActive);
if (hasMember) {
const memberIndex = board.members.findIndex(m => m.userId === userId);
@ -3593,7 +3593,7 @@ if (Meteor.isServer) {
check(userData, Object);
return sanitizeUserForSearch(userData);
},
searchUsers(query, boardId) {
async searchUsers(query, boardId) {
check(query, String);
check(boardId, String);
@ -3601,8 +3601,8 @@ if (Meteor.isServer) {
throw new Meteor.Error('not-logged-in', 'User must be logged in');
}
const currentUser = ReactiveCache.getCurrentUser();
const board = ReactiveCache.getBoard(boardId);
const currentUser = await ReactiveCache.getCurrentUser();
const board = await ReactiveCache.getBoard(boardId);
// Check if current user is a member of the board
const member = _.find(board.members, function(member) { return member.userId === currentUser._id; });
@ -3615,7 +3615,7 @@ if (Meteor.isServer) {
}
const searchRegex = new RegExp(query, 'i');
const users = ReactiveCache.getUsers({
const users = await ReactiveCache.getUsers({
$or: [
{ username: searchRegex },
{ 'profile.fullname': searchRegex },

View file

@ -244,14 +244,14 @@ export class WekanCreator {
]);
}
getMembersToMap(data) {
async getMembersToMap(data) {
// we will work on the list itself (an ordered array of objects) when a
// mapping is done, we add a 'wekan' field to the object representing the
// imported member
const membersToMap = data.members;
const users = data.users;
// auto-map based on username
membersToMap.forEach(importedMember => {
for (const importedMember of membersToMap) {
importedMember.id = importedMember.userId;
delete importedMember.userId;
const user = users.filter(user => {
@ -261,11 +261,11 @@ export class WekanCreator {
importedMember.fullName = user.profile.fullname;
}
importedMember.username = user.username;
const wekanUser = ReactiveCache.getUser({ username: importedMember.username });
const wekanUser = await ReactiveCache.getUser({ username: importedMember.username });
if (wekanUser) {
importedMember.wekanId = wekanUser._id;
}
});
}
return membersToMap;
}
@ -280,7 +280,7 @@ export class WekanCreator {
}
// You must call parseActions before calling this one.
createBoardAndLabels(boardToImport) {
async createBoardAndLabels(boardToImport) {
const boardToCreate = {
archived: boardToImport.archived,
color: boardToImport.color,
@ -304,7 +304,7 @@ export class WekanCreator {
permission: boardToImport.permission,
slug: getSlug(boardToImport.title) || 'board',
stars: 0,
title: Boards.uniqueTitle(boardToImport.title),
title: await Boards.uniqueTitle(boardToImport.title),
};
// now add other members
if (boardToImport.members) {
@ -665,8 +665,8 @@ export class WekanCreator {
});
}
createSubtasks(wekanCards) {
wekanCards.forEach(card => {
async createSubtasks(wekanCards) {
for (const card of wekanCards) {
// get new id of card (in created / new board)
const cardIdInNewBoard = this.cards[card._id];
@ -683,7 +683,7 @@ export class WekanCreator {
: card.parentId;
//if the parent card exists, proceed
if (ReactiveCache.getCard(parentIdInNewBoard)) {
if (await ReactiveCache.getCard(parentIdInNewBoard)) {
//set parent id of the card in the new board to the new id of the parent
Cards.direct.update(cardIdInNewBoard, {
$set: {
@ -691,7 +691,7 @@ export class WekanCreator {
},
});
}
});
}
}
createChecklists(wekanChecklists) {
@ -978,16 +978,16 @@ export class WekanCreator {
Meteor.settings.public &&
Meteor.settings.public.sandstorm;
if (isSandstorm && currentBoardId) {
const currentBoard = ReactiveCache.getBoard(currentBoardId);
const currentBoard = await ReactiveCache.getBoard(currentBoardId);
await currentBoard.archive();
}
this.parseActivities(board);
const boardId = this.createBoardAndLabels(board);
const boardId = await this.createBoardAndLabels(board);
this.createLists(board.lists, boardId);
this.createSwimlanes(board.swimlanes, boardId);
this.createCustomFields(board.customFields, boardId);
this.createCards(board.cards, boardId);
this.createSubtasks(board.cards);
await this.createSubtasks(board.cards);
this.createChecklists(board.checklists);
this.createChecklistItems(board.checklistItems);
this.importActivities(board.activities, boardId);

View file

@ -1,13 +1,13 @@
import { ReactiveCache } from '/imports/reactiveCache';
export function getMembersToMap(data) {
export async function getMembersToMap(data) {
// we will work on the list itself (an ordered array of objects) when a
// mapping is done, we add a 'wekan' field to the object representing the
// imported member
const membersToMap = data.members;
const users = data.users;
// auto-map based on username
membersToMap.forEach(importedMember => {
for (const importedMember of membersToMap) {
importedMember.id = importedMember.userId;
delete importedMember.userId;
const user = users.filter(user => {
@ -17,10 +17,10 @@ export function getMembersToMap(data) {
importedMember.fullName = user.profile.fullname;
}
importedMember.username = user.username;
const wekanUser = ReactiveCache.getUser({ username: importedMember.username });
const wekanUser = await ReactiveCache.getUser({ username: importedMember.username });
if (wekanUser) {
importedMember.wekanId = wekanUser._id;
}
});
}
return membersToMap;
}

View file

@ -0,0 +1,8 @@
*.swp
*~
*.iml
.*.haste_cache.*
.DS_Store
.idea
npm-debug.log
node_modules

View file

@ -0,0 +1,22 @@
sudo: false
language: node_js
node_js:
- "4.8.4"
addons:
apt:
packages:
- xvfb
install:
- export DISPLAY=':99.0'
- Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
- npm install
before_install:
- curl https://install.meteor.com/?release=1.6.1.1 | /bin/sh
- export PATH="$HOME/.meteor:$PATH"
script:
- (cd tests && meteor npm install)
- npm run lint
- npm test

View file

@ -0,0 +1,50 @@
# ChangeLog
# v1.0.7 2026-01-05 meteor-reactive-cache release
This release adds the following updates:
- Updated dependencies.
[Part 1](https://github.com/wekan/meteor-reactive-cache/commit/eed764fb54428224a970e96e5ea12a64470ea1d2),
[Part 2](https://github.com/wekan/meteor-reactive-cache/commit/32a496271ed45c2b3a8a26d6bde878e14b113637).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v1.0.6 2023-07-19 meteor-reactive-cache release
This release adds the following updates:
- [Updated dependencies](https://github.com/wekan/meteor-reactive-cache/commit/63c2ecc549e5c985be70af70a11ae4ac614e3455).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v1.0.5 2023-07-19 meteor-reactive-cache release
This release fixes the following bugs:
- [Fixed using newer fixed @wekanteam/meteor-globals package](https://github.com/wekan/meteor-reactive-cache/commit/1fe7a07c8607419c86bceabce5ca024432435fc2).
Thanks to xet7.
- [Renamed publish to release. Added release script](https://github.com/wekan/meteor-reactive-cache/commit/e43c232453c0d7267576c82c6f6463ede34a2c55).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v1.0.4 2023-07-19 meteor-reactive-cache release
This release fixes the following bugs:
- [Added PUBLISH.md about missing steps to publish npm package. Added missing published files also to git repo](https://github.com/wekan/meteor-reactive-cache/commit/b355ca815ebf9389d3a0dd57cddee7938aa4bf0c).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.
# v1.0.3 2023-07-19 meteor-reactive-cache release
This release adds the following updates:
- [Updated dependencies in this package and @wekanteam/meteor-globals](https://github.com/wekan/meteor-reactive-cache/commit/659a4e51c11bd95ec2fc5dccfb0bf1003ca7737d).
Thanks to xet7.
Thanks to above GitHub users for their contributions and translators for their translations.

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 Max Nowack
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,72 @@
# meteor-reactive-cache [![Build Status](https://travis-ci.org/maxnowack/meteor-reactive-cache.svg?branch=master)](https://travis-ci.org/maxnowack/meteor-reactive-cache)
Utilities for caching reactive data
### Installation
````bash
 $ npm install --save meteor-reactive-cache
````
### Usage
#### `ReactiveCache(compare: function)`
A simple reactive cache. It haves the same API like a `ReactiveDict`, but the values are getting deleted if all wrapped computations are stopped.
````es6
import { Tracker } from 'meteor/tracker'
import { ReactiveCache } from 'meteor-reactive-cache'
const reactiveCache = new ReactiveCache(/* compareFn */);
reactiveCache.set('foo', 'bar');
const computation = Tracker.autorun(() => {
reactiveCache.get('foo'); // reactive!
})
reactiveCache.set('foo', 'new bar');
computation.stop(); // keys will be invalidated if they don't have reactive dependants
reactiveCache.get('foo'); // undefined
````
#### `DataCache(resolve: function, { timeout: number, compare: function })`
Provides a simple reactive data cache, by passing in a function, that resolves a key to data in a reactive context.
````es6
import { Tracker } from 'meteor/tracker'
import { DataCache } from 'meteor-reactive-cache'
const dataCache = new DataCache((key) => {
// do some expensive reactive work here, which returns the same data for the same key.
// this function will only be executed if a reactive dependency changes or the requested key isn't cached.
})
const computation = Tracker.autorun(() => {
reactiveCache.get('foo'); // reactive!
})
computation.stop(); // keys will be invalidated if they don't have reactive dependants
reactiveCache.get('foo'); // undefined
````
#### `reactiveField(resolve: function, { timeout: number, compare: function })`
Like DataCache, but with a much simpler API and support for multiple function parameters.
````es6
import { Tracker } from 'meteor/tracker'
import { reactiveField } from 'meteor-reactive-cache'
const field = reactiveField((val1, val2, val3) => {
// …
})
const computation = Tracker.autorun(() => {
field('foo', 'bar', 1234); // reactive!
})
````
## License
Licensed under MIT license. Copyright (c) 2017 Max Nowack
## Contributions
Contributions are welcome. Please open issues and/or file Pull Requests.
## Maintainers
- Max Nowack ([maxnowack](https://github.com/maxnowack))

View file

@ -0,0 +1,14 @@
# Steps to release
## 1. Update these for new version info
- package.json: updated dependencies and new version info
- package-lock.json: updated dependencies and 2x new version info
- CHANGELOG.md: Newest changes and new version info
- Optionally: source code at src/ directory
## 2. Release new version number
```
./release.sh 1.0.5
```

View file

@ -0,0 +1,92 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _bindEnvironment = _interopRequireDefault(require("./meteor/bindEnvironment"));
var _tracker = _interopRequireDefault(require("./meteor/tracker"));
var _ReactiveCache = _interopRequireDefault(require("./ReactiveCache"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
var DataCache = exports["default"] = /*#__PURE__*/function () {
function DataCache(getData, options) {
_classCallCheck(this, DataCache);
this.options = _objectSpread({
timeout: 60 * 1000
}, typeof options === 'function' ? {
compare: options
} : options);
this.getData = getData;
this.cache = new _ReactiveCache["default"](this.options.compare, function () {
return false;
});
this.timeouts = {};
this.computations = {};
}
return _createClass(DataCache, [{
key: "ensureComputation",
value: function ensureComputation(key) {
var _this = this;
if (this.timeouts[key]) {
clearTimeout(this.timeouts[key]);
delete this.timeouts[key];
}
if (this.computations[key] && !this.computations[key].stopped) return;
this.computations[key] = _tracker["default"].nonreactive(function () {
return _tracker["default"].autorun(function () {
_this.cache.set(key, _this.getData(key));
});
});
// stop the computation if the key doesn't have any dependants
this.computations[key].onInvalidate(function () {
return _this.checkStop(key);
});
}
}, {
key: "checkStop",
value: function checkStop(key) {
var _this2 = this;
if (this.cache.ensureDependency(key).hasDependents()) return;
if (this.timeouts[key]) {
clearTimeout(this.timeouts[key]);
delete this.timeouts[key];
}
this.timeouts[key] = setTimeout((0, _bindEnvironment["default"])(function () {
if (!_this2.computations[key]) return;
_this2.computations[key].stop();
delete _this2.computations[key];
_this2.cache.del(key);
}), this.options.timeout);
}
}, {
key: "get",
value: function get(key) {
var _this3 = this;
if (!_tracker["default"].currentComputation) {
var _data = this.cache.get(key);
if (_data == null) {
_data = this.getData(key);
this.cache.set(key, _data);
this.checkStop(key);
}
return _data;
}
this.ensureComputation(key);
var data = this.cache.get(key);
_tracker["default"].currentComputation.onStop(function () {
return _this3.checkStop(key);
});
return data;
}
}]);
}();

View file

@ -0,0 +1,84 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _tracker = _interopRequireDefault(require("./meteor/tracker"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
var ReactiveCache = exports["default"] = /*#__PURE__*/function () {
function ReactiveCache(compare, shouldStop) {
_classCallCheck(this, ReactiveCache);
this.shouldStop = shouldStop || function () {
return true;
};
this.compare = compare || function (a, b) {
return a === b;
};
this.values = {};
this.deps = {};
}
return _createClass(ReactiveCache, [{
key: "ensureDependency",
value: function ensureDependency(key) {
if (!this.deps[key]) this.deps[key] = new _tracker["default"].Dependency();
return this.deps[key];
}
}, {
key: "checkDeletion",
value: function checkDeletion(key) {
var dep = this.ensureDependency(key);
if (dep.hasDependents()) return false;
delete this.values[key];
delete this.deps[key];
return true;
}
}, {
key: "clear",
value: function clear() {
var _this = this;
Object.keys(this.values).forEach(function (key) {
return _this.del(key);
});
}
}, {
key: "del",
value: function del(key) {
var dep = this.ensureDependency(key);
delete this.values[key];
if (this.checkDeletion(key)) return;
dep.changed();
}
}, {
key: "set",
value: function set(key, data, bypassCompare) {
var dep = this.ensureDependency(key);
var current = this.values[key];
this.values[key] = data;
if (!this.compare(current, data) || bypassCompare) {
dep.changed();
}
}
}, {
key: "get",
value: function get(key) {
var _this2 = this;
var data = this.values[key];
if (_tracker["default"].currentComputation) {
var dep = this.ensureDependency(key);
dep.depend();
_tracker["default"].currentComputation.onStop(function () {
if (!_this2.shouldStop(key)) return;
_this2.checkDeletion(key);
});
}
return data;
}
}]);
}();

View file

@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "DataCache", {
enumerable: true,
get: function get() {
return _DataCache["default"];
}
});
Object.defineProperty(exports, "ReactiveCache", {
enumerable: true,
get: function get() {
return _ReactiveCache["default"];
}
});
Object.defineProperty(exports, "reactiveField", {
enumerable: true,
get: function get() {
return _reactiveField["default"];
}
});
var _DataCache = _interopRequireDefault(require("./DataCache"));
var _ReactiveCache = _interopRequireDefault(require("./ReactiveCache"));
var _reactiveField = _interopRequireDefault(require("./reactiveField"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }

View file

@ -0,0 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _meteorGlobals = require("@wekanteam/meteor-globals");
var Meteor = (0, _meteorGlobals.getGlobal)('meteor', 'Meteor');
var _default = exports["default"] = Meteor.bindEnvironment.bind(Meteor);

View file

@ -0,0 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _meteorGlobals = require("@wekanteam/meteor-globals");
var _default = exports["default"] = (0, _meteorGlobals.getGlobal)('ejson', 'EJSON');

View file

@ -0,0 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _meteorGlobals = require("@wekanteam/meteor-globals");
var _default = exports["default"] = (0, _meteorGlobals.getGlobal)('tracker', 'Tracker');

View file

@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _DataCache = _interopRequireDefault(require("./DataCache"));
var _ejson = _interopRequireDefault(require("./meteor/ejson"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
var _default = exports["default"] = function _default(fn, compare) {
var cache = new _DataCache["default"](function (key) {
return fn.apply(void 0, _toConsumableArray(_ejson["default"].parse(key)));
}, compare);
var resolver = function resolver() {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return cache.get(_ejson["default"].stringify(args));
};
resolver.cache = cache;
return resolver;
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,63 @@
{
"name": "@wekanteam/meteor-reactive-cache",
"version": "1.0.7",
"description": "Utilities for caching reactive data",
"main": "dist/index.js",
"scripts": {
"prepublish": "./node_modules/.bin/babel src -d dist",
"lint": "./node_modules/.bin/eslint src",
"copy": "npm link && cd tests && npm link meteor-reactive-cache",
"test": "npm run copy && cd tests && TEST_BROWSER_DRIVER=nightmare meteor test -p 3100 --once --driver-package meteortesting:mocha"
},
"repository": {
"type": "git",
"url": "git+https://github.com/maxnowack/meteor-reactive-cache.git"
},
"author": "Max Nowack <max@unsou.de>",
"license": "MIT",
"bugs": {
"url": "https://github.com/maxnowack/meteor-reactive-cache/issues"
},
"homepage": "https://github.com/maxnowack/meteor-reactive-cache#readme",
"files": [
"dist"
],
"keywords": [
"caching",
"meteor",
"react",
"reactivity",
"tracker"
],
"babel": {
"presets": [
"@babel/preset-env"
]
},
"eslintConfig": {
"extends": [
"airbnb-base"
],
"parser": "@babel/eslint-parser",
"rules": {
"no-param-reassign": [
2,
{
"props": false
}
]
}
},
"devDependencies": {
"@babel/cli": "^7.23.9",
"@babel/core": "^7.23.9",
"@babel/eslint-parser": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"eslint": "^8.56.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.29.1"
},
"dependencies": {
"@wekanteam/meteor-globals": "^1.1.6"
}
}

View file

@ -0,0 +1,25 @@
#!/bin/bash
## 1. Copy files
npm run prepublish
npm run copy
## 2. Commit
git add --all
git commit -m "$1"
## 3. Add tags
git tag -a $1 -m "$1"
git push origin $1
git push
## 4. Publish npm package
npm publish --access public

View file

@ -0,0 +1,61 @@
import bindEnvironment from './meteor/bindEnvironment';
import Tracker from './meteor/tracker';
import ReactiveCache from './ReactiveCache';
export default class DataCache {
constructor(getData, options) {
this.options = {
timeout: 60 * 1000, // 60 seconds
...(typeof options === 'function' ? { compare: options } : options),
};
this.getData = getData;
this.cache = new ReactiveCache(this.options.compare, () => false);
this.timeouts = {};
this.computations = {};
}
ensureComputation(key) {
if (this.timeouts[key]) {
clearTimeout(this.timeouts[key]);
delete this.timeouts[key];
}
if (this.computations[key] && !this.computations[key].stopped) return;
this.computations[key] = Tracker.nonreactive(() => Tracker.autorun(() => {
this.cache.set(key, this.getData(key));
}));
// stop the computation if the key doesn't have any dependants
this.computations[key].onInvalidate(() => this.checkStop(key));
}
checkStop(key) {
if (this.cache.ensureDependency(key).hasDependents()) return;
if (this.timeouts[key]) {
clearTimeout(this.timeouts[key]);
delete this.timeouts[key];
}
this.timeouts[key] = setTimeout(bindEnvironment(() => {
if (!this.computations[key]) return;
this.computations[key].stop();
delete this.computations[key];
this.cache.del(key);
}), this.options.timeout);
}
get(key) {
if (!Tracker.currentComputation) {
let data = this.cache.get(key);
if (data == null) {
data = this.getData(key);
this.cache.set(key, data);
this.checkStop(key);
}
return data;
}
this.ensureComputation(key);
const data = this.cache.get(key);
Tracker.currentComputation.onStop(() => this.checkStop(key));
return data;
}
}

View file

@ -0,0 +1,56 @@
import Tracker from './meteor/tracker';
export default class ReactiveCache {
constructor(compare, shouldStop) {
this.shouldStop = shouldStop || (() => true);
this.compare = compare || ((a, b) => a === b);
this.values = {};
this.deps = {};
}
ensureDependency(key) {
if (!this.deps[key]) this.deps[key] = new Tracker.Dependency();
return this.deps[key];
}
checkDeletion(key) {
const dep = this.ensureDependency(key);
if (dep.hasDependents()) return false;
delete this.values[key];
delete this.deps[key];
return true;
}
clear() {
Object.keys(this.values).forEach((key) => this.del(key));
}
del(key) {
const dep = this.ensureDependency(key);
delete this.values[key];
if (this.checkDeletion(key)) return;
dep.changed();
}
set(key, data, bypassCompare) {
const dep = this.ensureDependency(key);
const current = this.values[key];
this.values[key] = data;
if (!this.compare(current, data) || bypassCompare) {
dep.changed();
}
}
get(key) {
const data = this.values[key];
if (Tracker.currentComputation) {
const dep = this.ensureDependency(key);
dep.depend();
Tracker.currentComputation.onStop(() => {
if (!this.shouldStop(key)) return;
this.checkDeletion(key);
});
}
return data;
}
}

View file

@ -0,0 +1,3 @@
export { default as DataCache } from './DataCache';
export { default as ReactiveCache } from './ReactiveCache';
export { default as reactiveField } from './reactiveField';

View file

@ -0,0 +1,4 @@
import { getGlobal } from '@wekanteam/meteor-globals';
const Meteor = getGlobal('meteor', 'Meteor');
export default Meteor.bindEnvironment.bind(Meteor);

View file

@ -0,0 +1,3 @@
import { getGlobal } from '@wekanteam/meteor-globals';
export default getGlobal('ejson', 'EJSON');

View file

@ -0,0 +1,3 @@
import { getGlobal } from '@wekanteam/meteor-globals';
export default getGlobal('tracker', 'Tracker');

View file

@ -0,0 +1,9 @@
import DataCache from './DataCache';
import ejson from './meteor/ejson';
export default (fn, compare) => {
const cache = new DataCache((key) => fn(...ejson.parse(key)), compare);
const resolver = (...args) => cache.get(ejson.stringify(args));
resolver.cache = cache;
return resolver;
};

View file

View file

@ -0,0 +1,18 @@
# This file contains information which helps Meteor properly upgrade your
# app when you run 'meteor update'. You should check it into version control
# with your project.
notices-for-0.9.0
notices-for-0.9.1
0.9.4-platform-file
notices-for-facebook-graph-api-2
1.2.0-standard-minifiers-package
1.2.0-meteor-platform-split
1.2.0-cordova-changes
1.2.0-breaking-changes
1.3.0-split-minifiers-package
1.4.0-remove-old-dev-bundle-link
1.4.1-add-shell-server-package
1.4.3-split-account-service-packages
1.5-add-dynamic-import-package
1.7-split-underscore-from-meteor-base

View file

@ -0,0 +1 @@
local

View file

@ -0,0 +1,7 @@
# This file contains a token that is unique to your project.
# Check it into your repository along with the rest of this directory.
# It can be used for purposes such as:
# - ensuring you don't accidentally deploy one app on top of another
# - providing package authors with aggregated statistics
1efzo9epsn1ob1vv1hyv

View file

@ -0,0 +1,18 @@
# Meteor packages used by this project, one per line.
# Check this file (and the other files in this directory) into your repository.
#
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
meteor-base@1.4.0 # Packages every Meteor app needs to have
reactive-var@1.0.11 # Reactive variable for tracker
tracker@1.2.0 # Meteor's client-side reactive programming library
standard-minifier-css@1.5.2 # CSS minifier run for production mode
standard-minifier-js@2.4.0 # JS minifier run for production mode
es5-shim@4.8.0 # ECMAScript 5 compatibility for older browsers.
ecmascript@0.12.3 # Enable ECMAScript2015+ syntax in app code
shell-server@0.4.0 # Server-side component of the `meteor shell` command
reactive-dict@1.2.1
dynamic-import@0.5.0
meteortesting:mocha

View file

@ -0,0 +1,2 @@
server
browser

View file

@ -0,0 +1 @@
METEOR@1.8.0.1

View file

@ -0,0 +1,63 @@
allow-deny@1.1.0
autoupdate@1.5.0
babel-compiler@7.2.3
babel-runtime@1.3.0
base64@1.0.11
binary-heap@1.0.11
boilerplate-generator@1.6.0
callback-hook@1.1.0
check@1.3.1
ddp@1.4.0
ddp-client@2.3.3
ddp-common@1.4.0
ddp-server@2.2.0
diff-sequence@1.1.1
dynamic-import@0.5.1
ecmascript@0.12.3
ecmascript-runtime@0.7.0
ecmascript-runtime-client@0.8.0
ecmascript-runtime-server@0.7.1
ejson@1.1.0
es5-shim@4.8.0
fetch@0.1.0
geojson-utils@1.0.10
hot-code-push@1.0.4
http@1.4.2
id-map@1.1.0
inter-process-messaging@0.1.0
livedata@1.0.18
lmieulet:meteor-coverage@2.0.2
logging@1.1.20
meteor@1.9.2
meteor-base@1.4.0
meteortesting:browser-tests@1.2.0
meteortesting:mocha@1.1.0
meteortesting:mocha-core@5.2.0_3
minifier-css@1.4.1
minifier-js@2.4.0
minimongo@1.4.5
modern-browsers@0.1.3
modules@0.13.0
modules-runtime@0.10.3
mongo@1.6.0
mongo-decimal@0.1.0
mongo-dev-server@1.1.0
mongo-id@1.0.7
npm-mongo@3.1.1
ordered-dict@1.1.0
promise@0.11.1
random@1.1.0
reactive-dict@1.2.1
reactive-var@1.0.11
reload@1.2.0
retry@1.1.0
routepolicy@1.1.0
shell-server@0.4.0
socket-stream-client@0.2.2
standard-minifier-css@1.5.2
standard-minifier-js@2.4.0
tracker@1.2.0
underscore@1.0.10
url@1.2.0
webapp@1.7.2
webapp-hashing@1.0.9

View file

@ -0,0 +1,54 @@
/* global describe it */
import chai from 'chai';
import { DataCache } from 'meteor-reactive-cache';
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { ReactiveDict } from 'meteor/reactive-dict';
describe('DataCache', () => {
it('should be reactive', (done) => {
const reactiveDict = new ReactiveDict();
reactiveDict.set('foo', '000');
let numGets = 0;
const reactiveCache = new DataCache((key) => {
numGets += 1;
return reactiveDict.get(key);
}, {
timeout: 10,
});
let runs = 0;
const computation = Tracker.autorun(() => {
runs += 1;
const value = reactiveCache.get('foo'); // eslint-disable-line
});
chai.assert.equal(reactiveCache.get('foo'), '000');
chai.assert.equal(numGets, 1);
chai.assert.equal(runs, 1);
reactiveDict.set('foo', 'bar');
Tracker.flush({ _throwFirstError: true });
chai.assert.equal(reactiveCache.get('foo'), 'bar');
chai.assert.equal(numGets, 2);
chai.assert.equal(runs, 2);
reactiveDict.set('foo', 'bar');
Tracker.flush({ _throwFirstError: true });
chai.assert.equal(reactiveCache.get('foo'), 'bar');
chai.assert.equal(numGets, 2);
chai.assert.equal(runs, 2);
computation.stop();
chai.assert.equal(reactiveCache.get('foo'), 'bar');
chai.assert.equal(numGets, 2);
Meteor.setTimeout(() => {
chai.assert.equal(reactiveCache.get('foo'), 'bar');
chai.assert.equal(numGets, 3);
done();
}, 50);
});
});

View file

@ -0,0 +1,89 @@
/* global describe it */
import chai from 'chai';
import { ReactiveCache } from 'meteor-reactive-cache';
import { Tracker } from 'meteor/tracker';
import isEqual from 'lodash.isequal';
describe('ReactiveCache', () => {
it('should be reactive', () => {
const reactiveCache = new ReactiveCache();
let runs = 0;
Tracker.autorun(() => {
runs += 1;
const value = reactiveCache.get('foo'); // eslint-disable-line
});
chai.assert.equal(typeof reactiveCache.get('foo'), 'undefined');
chai.assert.equal(runs, 1);
reactiveCache.set('foo', 'bar');
Tracker.flush({ _throwFirstError: true });
chai.assert.equal(reactiveCache.get('foo'), 'bar');
chai.assert.equal(runs, 2);
reactiveCache.set('foo', 'bar');
Tracker.flush({ _throwFirstError: true });
chai.assert.equal(reactiveCache.get('foo'), 'bar');
chai.assert.equal(runs, 2);
reactiveCache.del('foo');
Tracker.flush({ _throwFirstError: true });
chai.assert.equal(typeof reactiveCache.get('foo'), 'undefined');
chai.assert.equal(runs, 3);
});
it('should be reactive with custom compare', () => {
const reactiveCache = new ReactiveCache();
const reactiveObjCache = new ReactiveCache((a, b) => isEqual(a, b));
reactiveCache.set('foo', { test: true });
reactiveObjCache.set('foo', { test: true });
let runs = 0;
Tracker.autorun(() => {
runs += 1;
const value = reactiveCache.get('foo'); // eslint-disable-line
});
let runsObj = 0;
Tracker.autorun(() => {
runsObj += 1;
const value = reactiveObjCache.get('foo'); // eslint-disable-line
});
chai.assert.equal(runs, 1);
chai.assert.equal(runsObj, 1);
reactiveCache.set('foo', { test: true });
reactiveObjCache.set('foo', { test: true });
Tracker.flush({ _throwFirstError: true });
chai.assert.equal(runs, 2);
chai.assert.equal(runsObj, 1);
});
it('gets invalidated if computation stops', (done) => {
const reactiveCache = new ReactiveCache();
let runs = 0;
const computation = Tracker.autorun(() => {
runs += 1;
const value = reactiveCache.get('foo'); // eslint-disable-line
});
chai.assert.equal(typeof reactiveCache.get('foo'), 'undefined');
chai.assert.equal(runs, 1);
reactiveCache.set('foo', 'bar');
Tracker.flush({ _throwFirstError: true });
chai.assert.equal(reactiveCache.get('foo'), 'bar');
chai.assert.equal(runs, 2);
computation.stop();
chai.assert.equal(typeof reactiveCache.get('foo'), 'undefined');
setTimeout(() => {
done();
}, 200);
});
});

View file

@ -0,0 +1,154 @@
/* global describe it */
import chai from 'chai';
import { reactiveField } from 'meteor-reactive-cache';
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { ReactiveVar } from 'meteor/reactive-var';
import { ReactiveDict } from 'meteor/reactive-dict';
describe('reactiveField', () => {
it('should be reactive', (done) => {
const reactiveDict = new ReactiveDict();
reactiveDict.set('foo', '000');
let numGets = 0;
const field = reactiveField((key) => {
numGets += 1;
return reactiveDict.get(key);
}, {
timeout: 10,
});
let runs = 0;
const computation = Tracker.autorun(() => {
runs += 1;
const value = field('foo'); // eslint-disable-line
});
chai.assert.equal(field('foo'), '000');
chai.assert.equal(numGets, 1);
chai.assert.equal(runs, 1);
reactiveDict.set('foo', 'bar');
Tracker.flush({ _throwFirstError: true });
chai.assert.equal(field('foo'), 'bar');
chai.assert.equal(numGets, 2);
chai.assert.equal(runs, 2);
reactiveDict.set('foo', 'bar');
Tracker.flush({ _throwFirstError: true });
chai.assert.equal(field('foo'), 'bar');
chai.assert.equal(numGets, 2);
chai.assert.equal(runs, 2);
computation.stop();
chai.assert.equal(field('foo'), 'bar');
chai.assert.equal(numGets, 2);
Meteor.setTimeout(() => {
chai.assert.equal(field('foo'), 'bar');
chai.assert.equal(numGets, 3);
done();
}, 50);
});
it('should be reactive with complex values', () => {
const reactiveDict = new ReactiveDict();
reactiveDict.set('foo', { date: new Date('2018-01-01') });
let numGets = 0;
const field = reactiveField((key) => {
numGets += 1;
return reactiveDict.get(key);
}, {
timeout: 10,
});
let runs = 0;
const computation = Tracker.autorun(() => {
runs += 1;
const value = field('foo'); // eslint-disable-line
});
chai.assert.instanceOf(field('foo').date, Date);
chai.assert.equal(numGets, 1);
chai.assert.equal(runs, 1);
reactiveDict.set('foo', { date: new Date('2018-01-02') });
Tracker.flush({ _throwFirstError: true });
chai.assert.instanceOf(field('foo').date, Date);
chai.assert.equal(numGets, 2);
chai.assert.equal(runs, 2);
computation.stop();
});
it('should work with multiple parameters', () => {
const reactiveDict = new ReactiveDict();
reactiveDict.set('foofoo', '000');
reactiveDict.set('foobar', '000');
reactiveDict.set('barbar', '000');
let numGets = 0;
const field = reactiveField((val1, val2) => {
numGets += 1;
return reactiveDict.get(`${val1}${val2}`);
}, {
timeout: 100,
});
const runs = {
foofoo: 0,
foobar: 0,
barbar: 0,
};
Tracker.autorun(() => {
runs.foofoo += 1;
const value = field('foo', 'foo'); // eslint-disable-line
});
Tracker.autorun(() => {
runs.foobar += 1;
const value = field('foo', 'bar'); // eslint-disable-line
});
Tracker.autorun(() => {
runs.barbar += 1;
const value = field('bar', 'bar'); // eslint-disable-line
});
chai.assert.equal(field('foo', 'foo'), '000');
chai.assert.equal(numGets, 3);
chai.assert.equal(runs.foofoo, 1);
chai.assert.equal(field('foo', 'bar'), '000');
chai.assert.equal(numGets, 3);
chai.assert.equal(runs.foobar, 1);
chai.assert.equal(field('bar', 'bar'), '000');
chai.assert.equal(numGets, 3);
chai.assert.equal(runs.barbar, 1);
reactiveDict.set('foofoo', 'bar');
Tracker.flush({ _throwFirstError: true });
chai.assert.equal(field('foo', 'foo'), 'bar');
chai.assert.equal(numGets, 4);
chai.assert.equal(runs.foofoo, 2);
chai.assert.equal(runs.foobar, 1);
chai.assert.equal(runs.barbar, 1);
reactiveDict.set('foobar', 'bar');
Tracker.flush({ _throwFirstError: true });
chai.assert.equal(field('foo', 'bar'), 'bar');
chai.assert.equal(numGets, 5);
chai.assert.equal(runs.foofoo, 2);
chai.assert.equal(runs.foobar, 2);
chai.assert.equal(runs.barbar, 1);
reactiveDict.set('barbar', 'bar');
Tracker.flush({ _throwFirstError: true });
chai.assert.equal(field('bar', 'bar'), 'bar');
chai.assert.equal(numGets, 6);
chai.assert.equal(runs.foofoo, 2);
chai.assert.equal(runs.foobar, 2);
chai.assert.equal(runs.barbar, 2);
});
});

View file

@ -0,0 +1,40 @@
{
"name": "tests",
"private": true,
"scripts": {
"start": "meteor run"
},
"eslintConfig": {
"extends": [
"airbnb-base",
"plugin:meteor/recommended"
],
"plugins": [
"meteor"
],
"parser": "@babel/eslint-parser",
"rules": {
"meteor/audit-argument-checks": 0,
"import/no-extraneous-dependencies": 0,
"import/extensions": 0,
"import/no-unresolved": 0,
"no-underscore-dangle": 0
}
},
"dependencies": {
"@babel/runtime": "^7.26.0",
"meteor-node-stubs": "^1.2.13",
"meteor-reactive-cache": "^1.0.1"
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@babel/eslint-parser": "^7.25.9",
"chai": "^4.5.0",
"eslint": "^8.57.1",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-meteor": "^7.3.0",
"lodash.isequal": "^4.5.0",
"mocha": "^10.8.2"
}
}

4
package-lock.json generated
View file

@ -205,9 +205,7 @@
}
},
"@wekanteam/meteor-reactive-cache": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@wekanteam/meteor-reactive-cache/-/meteor-reactive-cache-1.0.7.tgz",
"integrity": "sha512-PSxoCX46sGcLygaKN/i/DrtPbKbm8AOnuNzK8lBE1BQTFkdnr7KBG2neGjFDbwLRHGmvvSfYStUmPtAk6xfx8w==",
"version": "file:npm-packages/meteor-reactive-cache",
"requires": {
"@wekanteam/meteor-globals": "^1.1.6"
}

View file

@ -28,7 +28,7 @@
"@wekanteam/exceljs": "^4.6.0",
"@wekanteam/html-to-markdown": "^1.0.2",
"@wekanteam/meteor-globals": "^1.1.6",
"@wekanteam/meteor-reactive-cache": "^1.0.7",
"@wekanteam/meteor-reactive-cache": "file:./npm-packages/meteor-reactive-cache",
"ajv": "^6.12.6",
"bcryptjs": "^2.4.3",
"bson": "^4.7.2",

View file

@ -12,7 +12,7 @@ import { ObjectID } from 'bson';
if (Meteor.isServer) {
Meteor.methods({
// Upload attachment via API
'api.attachment.upload'(boardId, swimlaneId, listId, cardId, fileData, fileName, fileType, storageBackend) {
async 'api.attachment.upload'(boardId, swimlaneId, listId, cardId, fileData, fileName, fileType, storageBackend) {
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
@ -23,12 +23,12 @@ if (Meteor.isServer) {
}
// Check if user has permission to modify the card
const card = ReactiveCache.getCard(cardId);
const card = await ReactiveCache.getCard(cardId);
if (!card) {
throw new Meteor.Error('card-not-found', 'Card not found');
}
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
throw new Meteor.Error('board-not-found', 'Board not found');
}
@ -114,19 +114,19 @@ if (Meteor.isServer) {
},
// Download attachment via API
'api.attachment.download'(attachmentId) {
async 'api.attachment.download'(attachmentId) {
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
// Get attachment
const attachment = ReactiveCache.getAttachment(attachmentId);
const attachment = await ReactiveCache.getAttachment(attachmentId);
if (!attachment) {
throw new Meteor.Error('attachment-not-found', 'Attachment not found');
}
// Check permissions
const board = ReactiveCache.getBoard(attachment.meta.boardId);
const board = await ReactiveCache.getBoard(attachment.meta.boardId);
if (!board || !board.isBoardMember(this.userId)) {
throw new Meteor.Error('not-authorized', 'You do not have permission to access this attachment');
}
@ -173,13 +173,13 @@ if (Meteor.isServer) {
},
// List attachments for board, swimlane, list, or card
'api.attachment.list'(boardId, swimlaneId, listId, cardId) {
async 'api.attachment.list'(boardId, swimlaneId, listId, cardId) {
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
// Check permissions
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board || !board.isBoardMember(this.userId)) {
throw new Meteor.Error('not-authorized', 'You do not have permission to access this board');
}
@ -199,8 +199,8 @@ if (Meteor.isServer) {
query['meta.cardId'] = cardId;
}
const attachments = ReactiveCache.getAttachments(query);
const attachments = await ReactiveCache.getAttachments(query);
const attachmentList = attachments.map(attachment => {
const strategy = fileStoreStrategyFactory.getFileStrategy(attachment, 'original');
return {
@ -230,25 +230,25 @@ if (Meteor.isServer) {
},
// Copy attachment to another card
'api.attachment.copy'(attachmentId, targetBoardId, targetSwimlaneId, targetListId, targetCardId) {
async 'api.attachment.copy'(attachmentId, targetBoardId, targetSwimlaneId, targetListId, targetCardId) {
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
// Get source attachment
const sourceAttachment = ReactiveCache.getAttachment(attachmentId);
const sourceAttachment = await ReactiveCache.getAttachment(attachmentId);
if (!sourceAttachment) {
throw new Meteor.Error('attachment-not-found', 'Source attachment not found');
}
// Check source permissions
const sourceBoard = ReactiveCache.getBoard(sourceAttachment.meta.boardId);
const sourceBoard = await ReactiveCache.getBoard(sourceAttachment.meta.boardId);
if (!sourceBoard || !sourceBoard.isBoardMember(this.userId)) {
throw new Meteor.Error('not-authorized', 'You do not have permission to access the source attachment');
}
// Check target permissions
const targetBoard = ReactiveCache.getBoard(targetBoardId);
const targetBoard = await ReactiveCache.getBoard(targetBoardId);
if (!targetBoard || !targetBoard.isBoardMember(this.userId)) {
throw new Meteor.Error('not-authorized', 'You do not have permission to modify the target card');
}
@ -328,25 +328,25 @@ if (Meteor.isServer) {
},
// Move attachment to another card
'api.attachment.move'(attachmentId, targetBoardId, targetSwimlaneId, targetListId, targetCardId) {
async 'api.attachment.move'(attachmentId, targetBoardId, targetSwimlaneId, targetListId, targetCardId) {
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
// Get source attachment
const sourceAttachment = ReactiveCache.getAttachment(attachmentId);
const sourceAttachment = await ReactiveCache.getAttachment(attachmentId);
if (!sourceAttachment) {
throw new Meteor.Error('attachment-not-found', 'Source attachment not found');
}
// Check source permissions
const sourceBoard = ReactiveCache.getBoard(sourceAttachment.meta.boardId);
const sourceBoard = await ReactiveCache.getBoard(sourceAttachment.meta.boardId);
if (!sourceBoard || !sourceBoard.isBoardMember(this.userId)) {
throw new Meteor.Error('not-authorized', 'You do not have permission to access the source attachment');
}
// Check target permissions
const targetBoard = ReactiveCache.getBoard(targetBoardId);
const targetBoard = await ReactiveCache.getBoard(targetBoardId);
if (!targetBoard || !targetBoard.isBoardMember(this.userId)) {
throw new Meteor.Error('not-authorized', 'You do not have permission to modify the target card');
}
@ -385,19 +385,19 @@ if (Meteor.isServer) {
},
// Delete attachment via API
'api.attachment.delete'(attachmentId) {
async 'api.attachment.delete'(attachmentId) {
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
// Get attachment
const attachment = ReactiveCache.getAttachment(attachmentId);
const attachment = await ReactiveCache.getAttachment(attachmentId);
if (!attachment) {
throw new Meteor.Error('attachment-not-found', 'Attachment not found');
}
// Check permissions
const board = ReactiveCache.getBoard(attachment.meta.boardId);
const board = await ReactiveCache.getBoard(attachment.meta.boardId);
if (!board || !board.isBoardMember(this.userId)) {
throw new Meteor.Error('not-authorized', 'You do not have permission to delete this attachment');
}
@ -419,19 +419,19 @@ if (Meteor.isServer) {
},
// Get attachment info via API
'api.attachment.info'(attachmentId) {
async 'api.attachment.info'(attachmentId) {
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
// Get attachment
const attachment = ReactiveCache.getAttachment(attachmentId);
const attachment = await ReactiveCache.getAttachment(attachmentId);
if (!attachment) {
throw new Meteor.Error('attachment-not-found', 'Attachment not found');
}
// Check permissions
const board = ReactiveCache.getBoard(attachment.meta.boardId);
const board = await ReactiveCache.getBoard(attachment.meta.boardId);
if (!board || !board.isBoardMember(this.userId)) {
throw new Meteor.Error('not-authorized', 'You do not have permission to access this attachment');
}

View file

@ -151,13 +151,13 @@ class AttachmentMigrationService {
async migrateAttachment(attachment) {
try {
// Get the card to find board and list information
const card = ReactiveCache.getCard(attachment.cardId);
const card = await ReactiveCache.getCard(attachment.cardId);
if (!card) {
console.warn(`Card not found for attachment ${attachment._id}`);
return;
}
const list = ReactiveCache.getList(card.listId);
const list = await ReactiveCache.getList(card.listId);
if (!list) {
console.warn(`List not found for attachment ${attachment._id}`);
return;
@ -259,12 +259,12 @@ Meteor.methods({
throw new Meteor.Error('not-authorized');
}
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
throw new Meteor.Error('board-not-found');
}
const user = ReactiveCache.getUser(this.userId);
const user = await ReactiveCache.getUser(this.userId);
const isBoardAdmin = board.hasAdmin(this.userId);
const isInstanceAdmin = user && user.isAdmin;
@ -275,14 +275,14 @@ Meteor.methods({
return await attachmentMigrationService.migrateBoardAttachments(boardId);
},
'attachmentMigration.getProgress'(boardId) {
async 'attachmentMigration.getProgress'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
@ -290,14 +290,14 @@ Meteor.methods({
return attachmentMigrationService.getMigrationProgress(boardId);
},
'attachmentMigration.getUnconvertedAttachments'(boardId) {
async 'attachmentMigration.getUnconvertedAttachments'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
@ -305,14 +305,14 @@ Meteor.methods({
return attachmentMigrationService.getUnconvertedAttachments(boardId);
},
'attachmentMigration.isBoardMigrated'(boardId) {
async 'attachmentMigration.isBoardMigrated'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}

View file

@ -15,13 +15,13 @@ Meteor.startup(() => {
Authentication = {};
Authentication.checkUserId = function(userId) {
Authentication.checkUserId = async function(userId) {
if (userId === undefined) {
const error = new Meteor.Error('Unauthorized', 'Unauthorized');
error.statusCode = 401;
throw error;
}
const admin = ReactiveCache.getUser({ _id: userId, isAdmin: true });
const admin = await ReactiveCache.getUser({ _id: userId, isAdmin: true });
if (admin === undefined) {
const error = new Meteor.Error('Forbidden', 'Forbidden');
@ -42,9 +42,9 @@ Meteor.startup(() => {
// An admin should be authorized to access everything, so we use a separate check for admins
// This throws an error if otherReq is false and the user is not an admin
Authentication.checkAdminOrCondition = function(userId, otherReq) {
Authentication.checkAdminOrCondition = async function(userId, otherReq) {
if (otherReq) return;
const admin = ReactiveCache.getUser({ _id: userId, isAdmin: true });
const admin = await ReactiveCache.getUser({ _id: userId, isAdmin: true });
if (admin === undefined) {
const error = new Meteor.Error('Forbidden', 'Forbidden');
error.statusCode = 403;
@ -53,19 +53,19 @@ Meteor.startup(() => {
};
// Helper function. Will throw an error if the user is not active BoardAdmin or active Normal user of the board.
Authentication.checkBoardAccess = function(userId, boardId) {
Authentication.checkBoardAccess = async function(userId, boardId) {
Authentication.checkLoggedIn(userId);
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
const normalAccess = board.members.some(e => e.userId === userId && e.isActive && !e.isNoComments && !e.isCommentOnly && !e.isWorker);
Authentication.checkAdminOrCondition(userId, normalAccess);
await Authentication.checkAdminOrCondition(userId, normalAccess);
};
// Helper function. Will throw an error if the user does not have write access to the board (excludes read-only users).
Authentication.checkBoardWriteAccess = function(userId, boardId) {
Authentication.checkBoardWriteAccess = async function(userId, boardId) {
Authentication.checkLoggedIn(userId);
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
const writeAccess = board.members.some(e => e.userId === userId && e.isActive && !e.isNoComments && !e.isCommentOnly && !e.isWorker && !e.isReadOnly && !e.isReadAssignedOnly);
Authentication.checkAdminOrCondition(userId, writeAccess);
await Authentication.checkAdminOrCondition(userId, writeAccess);
};
if (Meteor.isServer) {

View file

@ -1646,8 +1646,8 @@ class CronMigrationManager {
SyncedCron.add({
name: step.cronName,
schedule: (parser) => parser.text(step.schedule),
job: () => {
this.runMigrationStep(step);
job: async () => {
await this.runMigrationStep(step);
},
});
}
@ -2773,130 +2773,130 @@ Meteor.startup(() => {
// Meteor methods for client-server communication
Meteor.methods({
'cron.startAllMigrations'() {
async 'cron.startAllMigrations'() {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
return cronMigrationManager.startAllMigrations();
},
'cron.startSpecificMigration'(migrationIndex) {
async 'cron.startSpecificMigration'(migrationIndex) {
check(migrationIndex, Number);
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
return cronMigrationManager.startSpecificMigration(migrationIndex);
},
'cron.startJob'(cronName) {
async 'cron.startJob'(cronName) {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
return cronMigrationManager.startCronJob(cronName);
},
'cron.stopJob'(cronName) {
async 'cron.stopJob'(cronName) {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
return cronMigrationManager.stopCronJob(cronName);
},
'cron.pauseJob'(cronName) {
async 'cron.pauseJob'(cronName) {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
return cronMigrationManager.pauseCronJob(cronName);
},
'cron.resumeJob'(cronName) {
async 'cron.resumeJob'(cronName) {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
return cronMigrationManager.resumeCronJob(cronName);
},
'cron.removeJob'(cronName) {
async 'cron.removeJob'(cronName) {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
return cronMigrationManager.removeCronJob(cronName);
},
'cron.addJob'(jobData) {
async 'cron.addJob'(jobData) {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
return cronMigrationManager.addCronJob(jobData);
},
'cron.getJobs'() {
async 'cron.getJobs'() {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
return cronMigrationManager.getAllCronJobs();
},
'cron.getMigrationProgress'() {
async 'cron.getMigrationProgress'() {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -2970,12 +2970,12 @@ Meteor.methods({
};
},
'cron.pauseAllMigrations'() {
async 'cron.pauseAllMigrations'() {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -2983,12 +2983,12 @@ Meteor.methods({
return cronMigrationManager.pauseAllMigrations();
},
'cron.stopAllMigrations'() {
async 'cron.stopAllMigrations'() {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -2996,12 +2996,25 @@ Meteor.methods({
return cronMigrationManager.stopAllMigrations();
},
'cron.resumeAllMigrations'() {
async 'cron.stopAllMigrations'() {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
return cronMigrationManager.stopAllMigrations();
},
async 'cron.resumeAllMigrations'() {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -3009,12 +3022,12 @@ Meteor.methods({
return cronMigrationManager.resumeAllMigrations();
},
'cron.retryFailedMigrations'() {
async 'cron.retryFailedMigrations'() {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -3022,14 +3035,14 @@ Meteor.methods({
return cronMigrationManager.retryFailedMigrations();
},
'cron.getAllMigrationErrors'(limit = 50) {
async 'cron.getAllMigrationErrors'(limit = 50) {
check(limit, Match.Optional(Number));
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -3037,7 +3050,7 @@ Meteor.methods({
return cronMigrationManager.getAllMigrationErrors(limit);
},
'cron.getJobErrors'(jobId, options = {}) {
async 'cron.getJobErrors'(jobId, options = {}) {
check(jobId, String);
check(options, Match.Optional(Object));
@ -3045,7 +3058,7 @@ Meteor.methods({
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -3053,12 +3066,12 @@ Meteor.methods({
return cronMigrationManager.getJobErrors(jobId, options);
},
'cron.getMigrationStats'() {
async 'cron.getMigrationStats'() {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -3066,15 +3079,15 @@ Meteor.methods({
return cronMigrationManager.getMigrationStats();
},
'cron.startBoardOperation'(boardId, operationType, operationData) {
async 'cron.startBoardOperation'(boardId, operationType, operationData) {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
// Check if user is global admin OR board admin
const user = ReactiveCache.getUser(userId);
const board = ReactiveCache.getBoard(boardId);
const user = await ReactiveCache.getUser(userId);
const board = await ReactiveCache.getBoard(boardId);
if (!user) {
throw new Meteor.Error('not-authorized', 'User not found');
@ -3097,15 +3110,15 @@ Meteor.methods({
return cronMigrationManager.startBoardOperation(boardId, operationType, operationData);
},
'cron.getBoardOperations'(boardId) {
async 'cron.getBoardOperations'(boardId) {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
// Check if user is global admin OR board admin
const user = ReactiveCache.getUser(userId);
const board = ReactiveCache.getBoard(boardId);
const user = await ReactiveCache.getUser(userId);
const board = await ReactiveCache.getBoard(boardId);
if (!user) {
throw new Meteor.Error('not-authorized', 'User not found');
@ -3128,12 +3141,12 @@ Meteor.methods({
return cronMigrationManager.getBoardOperations(boardId);
},
'cron.getAllBoardOperations'(page, limit, searchTerm) {
async 'cron.getAllBoardOperations'(page, limit, searchTerm) {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -3141,12 +3154,12 @@ Meteor.methods({
return cronMigrationManager.getAllBoardOperations(page, limit, searchTerm);
},
'cron.getBoardOperationStats'() {
async 'cron.getBoardOperationStats'() {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -3154,12 +3167,12 @@ Meteor.methods({
return cronMigrationManager.getBoardOperationStats();
},
'cron.getJobDetails'(jobId) {
async 'cron.getJobDetails'(jobId) {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -3167,12 +3180,12 @@ Meteor.methods({
return cronJobStorage.getJobDetails(jobId);
},
'cron.getQueueStats'() {
async 'cron.getQueueStats'() {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -3180,12 +3193,12 @@ Meteor.methods({
return cronJobStorage.getQueueStats();
},
'cron.getSystemResources'() {
async 'cron.getSystemResources'() {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -3193,12 +3206,12 @@ Meteor.methods({
return cronJobStorage.getSystemResources();
},
'cron.clearAllJobs'() {
async 'cron.clearAllJobs'() {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -3206,12 +3219,12 @@ Meteor.methods({
return cronMigrationManager.clearAllCronJobs();
},
'cron.pauseJob'(jobId) {
async 'cron.pauseJob'(jobId) {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -3221,12 +3234,12 @@ Meteor.methods({
return { success: true };
},
'cron.resumeJob'(jobId) {
async 'cron.resumeJob'(jobId) {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -3236,12 +3249,12 @@ Meteor.methods({
return { success: true };
},
'cron.stopJob'(jobId) {
async 'cron.stopJob'(jobId) {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -3254,12 +3267,12 @@ Meteor.methods({
return { success: true };
},
'cron.cleanupOldJobs'(daysOld) {
async 'cron.cleanupOldJobs'(daysOld) {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -3267,12 +3280,76 @@ Meteor.methods({
return cronJobStorage.cleanupOldJobs(daysOld);
},
'cron.getBoardMigrationStats'() {
async 'cron.pauseAllMigrations'() {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
// Pause all running jobs in the queue
const runningJobs = cronJobStorage.getIncompleteJobs().filter(job => job.status === 'running');
runningJobs.forEach(job => {
cronJobStorage.updateQueueStatus(job.jobId, 'paused');
cronJobStorage.saveJobStatus(job.jobId, { status: 'paused' });
});
cronMigrationStatus.set('All migrations paused');
return { success: true, message: 'All migrations paused' };
},
async 'cron.stopAllMigrations'() {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
// Clear monitor interval first to prevent status override
if (cronMigrationManager.monitorInterval) {
Meteor.clearInterval(cronMigrationManager.monitorInterval);
cronMigrationManager.monitorInterval = null;
}
// Stop all running and pending jobs
const incompleteJobs = cronJobStorage.getIncompleteJobs();
incompleteJobs.forEach(job => {
cronJobStorage.updateQueueStatus(job.jobId, 'stopped', { stoppedAt: new Date() });
cronJobStorage.saveJobStatus(job.jobId, {
status: 'stopped',
stoppedAt: new Date()
});
});
// Reset migration state immediately
cronMigrationManager.isRunning = false;
cronIsMigrating.set(false);
cronMigrationProgress.set(0);
cronMigrationCurrentStep.set('');
cronMigrationCurrentStepNum.set(0);
cronMigrationTotalSteps.set(0);
cronMigrationStatus.set('All migrations stopped');
// Clear status message after delay
setTimeout(() => {
cronMigrationStatus.set('');
}, 3000);
return { success: true, message: 'All migrations stopped' };
},
async 'cron.getBoardMigrationStats'() {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}
@ -3282,12 +3359,12 @@ Meteor.methods({
return boardMigrationDetector.getMigrationStats();
},
'cron.forceBoardMigrationScan'() {
async 'cron.forceBoardMigrationScan'() {
const userId = this.userId;
if (!userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin access required');
}

View file

@ -17,13 +17,13 @@ EmailLocalization = {
* @param {String} options.language - Language code to use (if not provided, will try to detect)
* @param {String} options.userId - User ID to determine language (if not provided with language)
*/
sendEmail(options) {
async sendEmail(options) {
// Determine the language to use
let lang = options.language;
// If no language is specified but we have a userId, try to get the user's language
if (!lang && options.userId) {
const user = ReactiveCache.getUser(options.userId);
const user = await ReactiveCache.getUser(options.userId);
if (user) {
lang = user.getLanguage();
}

View file

@ -27,12 +27,12 @@ allowIsBoardMemberWithWriteAccess = function(userId, board) {
// Check if user has write access via a card's board
allowIsBoardMemberWithWriteAccessByCard = function(userId, card) {
const board = card && card.board && card.board();
const board = card && Boards.findOne(card.boardId);
return allowIsBoardMemberWithWriteAccess(userId, board);
};
allowIsBoardMemberByCard = function(userId, card) {
const board = card.board();
const board = card && Boards.findOne(card.boardId);
return board && board.hasMember(userId);
};

View file

@ -11,12 +11,12 @@ import { ReactiveCache } from '/imports/reactiveCache';
* This method identifies and removes duplicate lists while preserving cards
*/
Meteor.methods({
'fixDuplicateLists.fixAllBoards'() {
async 'fixDuplicateLists.fixAllBoards'() {
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
if (!ReactiveCache.getUser(this.userId).isAdmin) {
if (!(await ReactiveCache.getUser(this.userId)).isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin required');
}
@ -53,14 +53,14 @@ Meteor.methods({
};
},
'fixDuplicateLists.fixBoard'(boardId) {
async 'fixDuplicateLists.fixBoard'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board || !board.hasAdmin(this.userId)) {
throw new Meteor.Error('not-authorized');
}
@ -208,12 +208,12 @@ function fixDuplicateLists(boardId) {
}
Meteor.methods({
'fixDuplicateLists.getReport'() {
async 'fixDuplicateLists.getReport'() {
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
if (!ReactiveCache.getUser(this.userId).isAdmin) {
if (!(await ReactiveCache.getUser(this.userId)).isAdmin) {
throw new Meteor.Error('not-authorized', 'Admin required');
}

View file

@ -8,7 +8,7 @@ Meteor.methods({
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('error-not-allowed', 'Not allowed');
}
@ -56,7 +56,7 @@ Meteor.methods({
if (!adminId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user');
}
const admin = ReactiveCache.getUser(adminId);
const admin = await ReactiveCache.getUser(adminId);
if (!admin || !admin.isAdmin) {
throw new Meteor.Error('error-not-allowed', 'Not allowed');
}
@ -86,7 +86,7 @@ Meteor.methods({
if (!adminId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user');
}
const admin = ReactiveCache.getUser(adminId);
const admin = await ReactiveCache.getUser(adminId);
if (!admin || !admin.isAdmin) {
throw new Meteor.Error('error-not-allowed', 'Not allowed');
}

View file

@ -9,7 +9,7 @@ Meteor.methods({
if (!userId) {
throw new Meteor.Error('error-invalid-user', 'Invalid user');
}
const user = ReactiveCache.getUser(userId);
const user = await ReactiveCache.getUser(userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('error-not-allowed', 'Not allowed');
}

View file

@ -13,19 +13,19 @@ Meteor.methods({
/**
* Track original position for a swimlane
*/
'positionHistory.trackSwimlane'(swimlaneId) {
async 'positionHistory.trackSwimlane'(swimlaneId) {
check(swimlaneId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const swimlane = Swimlanes.findOne(swimlaneId);
const swimlane = await Swimlanes.findOneAsync(swimlaneId);
if (!swimlane) {
throw new Meteor.Error('swimlane-not-found', 'Swimlane not found');
}
const board = ReactiveCache.getBoard(swimlane.boardId);
const board = await ReactiveCache.getBoard(swimlane.boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
@ -36,19 +36,19 @@ Meteor.methods({
/**
* Track original position for a list
*/
'positionHistory.trackList'(listId) {
async 'positionHistory.trackList'(listId) {
check(listId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const list = Lists.findOne(listId);
const list = await Lists.findOneAsync(listId);
if (!list) {
throw new Meteor.Error('list-not-found', 'List not found');
}
const board = ReactiveCache.getBoard(list.boardId);
const board = await ReactiveCache.getBoard(list.boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
@ -59,19 +59,19 @@ Meteor.methods({
/**
* Track original position for a card
*/
'positionHistory.trackCard'(cardId) {
async 'positionHistory.trackCard'(cardId) {
check(cardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const card = Cards.findOne(cardId);
const card = await Cards.findOneAsync(cardId);
if (!card) {
throw new Meteor.Error('card-not-found', 'Card not found');
}
const board = ReactiveCache.getBoard(card.boardId);
const board = await ReactiveCache.getBoard(card.boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
@ -82,19 +82,19 @@ Meteor.methods({
/**
* Get original position for a swimlane
*/
'positionHistory.getSwimlaneOriginalPosition'(swimlaneId) {
async 'positionHistory.getSwimlaneOriginalPosition'(swimlaneId) {
check(swimlaneId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const swimlane = Swimlanes.findOne(swimlaneId);
const swimlane = await Swimlanes.findOneAsync(swimlaneId);
if (!swimlane) {
throw new Meteor.Error('swimlane-not-found', 'Swimlane not found');
}
const board = ReactiveCache.getBoard(swimlane.boardId);
const board = await ReactiveCache.getBoard(swimlane.boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
@ -105,19 +105,19 @@ Meteor.methods({
/**
* Get original position for a list
*/
'positionHistory.getListOriginalPosition'(listId) {
async 'positionHistory.getListOriginalPosition'(listId) {
check(listId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const list = Lists.findOne(listId);
const list = await Lists.findOneAsync(listId);
if (!list) {
throw new Meteor.Error('list-not-found', 'List not found');
}
const board = ReactiveCache.getBoard(list.boardId);
const board = await ReactiveCache.getBoard(list.boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
@ -128,19 +128,19 @@ Meteor.methods({
/**
* Get original position for a card
*/
'positionHistory.getCardOriginalPosition'(cardId) {
async 'positionHistory.getCardOriginalPosition'(cardId) {
check(cardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const card = Cards.findOne(cardId);
const card = await Cards.findOneAsync(cardId);
if (!card) {
throw new Meteor.Error('card-not-found', 'Card not found');
}
const board = ReactiveCache.getBoard(card.boardId);
const board = await ReactiveCache.getBoard(card.boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
@ -151,19 +151,19 @@ Meteor.methods({
/**
* Check if a swimlane has moved from its original position
*/
'positionHistory.hasSwimlaneMoved'(swimlaneId) {
async 'positionHistory.hasSwimlaneMoved'(swimlaneId) {
check(swimlaneId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const swimlane = Swimlanes.findOne(swimlaneId);
const swimlane = await Swimlanes.findOneAsync(swimlaneId);
if (!swimlane) {
throw new Meteor.Error('swimlane-not-found', 'Swimlane not found');
}
const board = ReactiveCache.getBoard(swimlane.boardId);
const board = await ReactiveCache.getBoard(swimlane.boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
@ -174,19 +174,19 @@ Meteor.methods({
/**
* Check if a list has moved from its original position
*/
'positionHistory.hasListMoved'(listId) {
async 'positionHistory.hasListMoved'(listId) {
check(listId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const list = Lists.findOne(listId);
const list = await Lists.findOneAsync(listId);
if (!list) {
throw new Meteor.Error('list-not-found', 'List not found');
}
const board = ReactiveCache.getBoard(list.boardId);
const board = await ReactiveCache.getBoard(list.boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
@ -197,19 +197,19 @@ Meteor.methods({
/**
* Check if a card has moved from its original position
*/
'positionHistory.hasCardMoved'(cardId) {
async 'positionHistory.hasCardMoved'(cardId) {
check(cardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const card = Cards.findOne(cardId);
const card = await Cards.findOneAsync(cardId);
if (!card) {
throw new Meteor.Error('card-not-found', 'Card not found');
}
const board = ReactiveCache.getBoard(card.boardId);
const board = await ReactiveCache.getBoard(card.boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
@ -220,19 +220,19 @@ Meteor.methods({
/**
* Get original position description for a swimlane
*/
'positionHistory.getSwimlaneDescription'(swimlaneId) {
async 'positionHistory.getSwimlaneDescription'(swimlaneId) {
check(swimlaneId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const swimlane = Swimlanes.findOne(swimlaneId);
const swimlane = await Swimlanes.findOneAsync(swimlaneId);
if (!swimlane) {
throw new Meteor.Error('swimlane-not-found', 'Swimlane not found');
}
const board = ReactiveCache.getBoard(swimlane.boardId);
const board = await ReactiveCache.getBoard(swimlane.boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
@ -243,19 +243,19 @@ Meteor.methods({
/**
* Get original position description for a list
*/
'positionHistory.getListDescription'(listId) {
async 'positionHistory.getListDescription'(listId) {
check(listId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const list = Lists.findOne(listId);
const list = await Lists.findOneAsync(listId);
if (!list) {
throw new Meteor.Error('list-not-found', 'List not found');
}
const board = ReactiveCache.getBoard(list.boardId);
const board = await ReactiveCache.getBoard(list.boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
@ -266,19 +266,19 @@ Meteor.methods({
/**
* Get original position description for a card
*/
'positionHistory.getCardDescription'(cardId) {
async 'positionHistory.getCardDescription'(cardId) {
check(cardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const card = Cards.findOne(cardId);
const card = await Cards.findOneAsync(cardId);
if (!card) {
throw new Meteor.Error('card-not-found', 'Card not found');
}
const board = ReactiveCache.getBoard(card.boardId);
const board = await ReactiveCache.getBoard(card.boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
@ -289,14 +289,14 @@ Meteor.methods({
/**
* Get all position history for a board
*/
'positionHistory.getBoardHistory'(boardId) {
async 'positionHistory.getBoardHistory'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
@ -305,13 +305,13 @@ Meteor.methods({
boardId: boardId,
}, {
sort: { createdAt: -1 }
}).fetch();
}).fetchAsync();
},
/**
* Get position history by entity type for a board
*/
'positionHistory.getBoardHistoryByType'(boardId, entityType) {
async 'positionHistory.getBoardHistoryByType'(boardId, entityType) {
check(boardId, String);
check(entityType, String);
@ -319,7 +319,7 @@ Meteor.methods({
throw new Meteor.Error('not-authorized', 'You must be logged in.');
}
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board || !board.isVisibleBy({ _id: this.userId })) {
throw new Meteor.Error('not-authorized', 'You do not have access to this board.');
}
@ -333,6 +333,6 @@ Meteor.methods({
entityType: entityType,
}, {
sort: { createdAt: -1 }
}).fetch();
}).fetchAsync();
},
});

View file

@ -41,9 +41,9 @@ class ComprehensiveBoardMigration {
/**
* Check if migration is needed for a board
*/
needsMigration(boardId) {
async needsMigration(boardId) {
try {
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) return false;
// Check if board has already been processed
@ -52,7 +52,7 @@ class ComprehensiveBoardMigration {
}
// Check for various issues that need migration
const issues = this.detectMigrationIssues(boardId);
const issues = await this.detectMigrationIssues(boardId);
return issues.length > 0;
} catch (error) {
@ -64,13 +64,13 @@ class ComprehensiveBoardMigration {
/**
* Detect all migration issues in a board
*/
detectMigrationIssues(boardId) {
async detectMigrationIssues(boardId) {
const issues = [];
try {
const cards = ReactiveCache.getCards({ boardId });
const lists = ReactiveCache.getLists({ boardId });
const swimlanes = ReactiveCache.getSwimlanes({ boardId });
const cards = await ReactiveCache.getCards({ boardId });
const lists = await ReactiveCache.getLists({ boardId });
const swimlanes = await ReactiveCache.getSwimlanes({ boardId });
// Issue 1: Cards with missing swimlaneId
const cardsWithoutSwimlane = cards.filter(card => !card.swimlaneId);
@ -157,7 +157,7 @@ class ComprehensiveBoardMigration {
console.log(`Starting comprehensive board migration for board ${boardId}`);
}
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
throw new Error(`Board ${boardId} not found`);
}
@ -288,7 +288,7 @@ class ComprehensiveBoardMigration {
* Step 1: Analyze board structure
*/
async analyzeBoardStructure(boardId) {
const issues = this.detectMigrationIssues(boardId);
const issues = await this.detectMigrationIssues(boardId);
return {
issues,
issueCount: issues.length,
@ -300,9 +300,9 @@ class ComprehensiveBoardMigration {
* Step 2: Fix orphaned cards (cards with missing swimlaneId or listId)
*/
async fixOrphanedCards(boardId, progressCallback = null) {
const cards = ReactiveCache.getCards({ boardId });
const swimlanes = ReactiveCache.getSwimlanes({ boardId });
const lists = ReactiveCache.getLists({ boardId });
const cards = await ReactiveCache.getCards({ boardId });
const swimlanes = await ReactiveCache.getSwimlanes({ boardId });
const lists = await ReactiveCache.getLists({ boardId });
let cardsFixed = 0;
const defaultSwimlane = swimlanes.find(s => s.title === 'Default') || swimlanes[0];
@ -370,9 +370,9 @@ class ComprehensiveBoardMigration {
* Step 3: Convert shared lists to per-swimlane lists
*/
async convertSharedListsToPerSwimlane(boardId, progressCallback = null) {
const cards = ReactiveCache.getCards({ boardId });
const lists = ReactiveCache.getLists({ boardId });
const swimlanes = ReactiveCache.getSwimlanes({ boardId });
const cards = await ReactiveCache.getCards({ boardId });
const lists = await ReactiveCache.getLists({ boardId });
const swimlanes = await ReactiveCache.getSwimlanes({ boardId });
let listsProcessed = 0;
let listsCreated = 0;
@ -475,8 +475,8 @@ class ComprehensiveBoardMigration {
* Step 4: Ensure all lists are per-swimlane
*/
async ensurePerSwimlaneLists(boardId) {
const lists = ReactiveCache.getLists({ boardId });
const swimlanes = ReactiveCache.getSwimlanes({ boardId });
const lists = await ReactiveCache.getLists({ boardId });
const swimlanes = await ReactiveCache.getSwimlanes({ boardId });
const defaultSwimlane = swimlanes.find(s => s.title === 'Default') || swimlanes[0];
let listsProcessed = 0;
@ -501,8 +501,8 @@ class ComprehensiveBoardMigration {
* Step 5: Cleanup empty lists (lists with no cards)
*/
async cleanupEmptyLists(boardId) {
const lists = ReactiveCache.getLists({ boardId });
const cards = ReactiveCache.getCards({ boardId });
const lists = await ReactiveCache.getLists({ boardId });
const cards = await ReactiveCache.getCards({ boardId });
let listsRemoved = 0;
@ -527,9 +527,9 @@ class ComprehensiveBoardMigration {
* Step 6: Validate migration
*/
async validateMigration(boardId) {
const issues = this.detectMigrationIssues(boardId);
const cards = ReactiveCache.getCards({ boardId });
const lists = ReactiveCache.getLists({ boardId });
const issues = await this.detectMigrationIssues(boardId);
const cards = await ReactiveCache.getCards({ boardId });
const lists = await ReactiveCache.getLists({ boardId });
// Check that all cards have valid swimlaneId and listId
const validCards = cards.filter(card => card.swimlaneId && card.listId);
@ -555,7 +555,7 @@ class ComprehensiveBoardMigration {
* Step 7: Fix avatar URLs (remove problematic auth parameters and fix URL formats)
*/
async fixAvatarUrls() {
const users = ReactiveCache.getUsers({});
const users = await ReactiveCache.getUsers({});
let avatarsFixed = 0;
for (const user of users) {
@ -610,7 +610,7 @@ class ComprehensiveBoardMigration {
* Step 8: Fix attachment URLs (remove problematic auth parameters and fix URL formats)
*/
async fixAttachmentUrls() {
const attachments = ReactiveCache.getAttachments({});
const attachments = await ReactiveCache.getAttachments({});
let attachmentsFixed = 0;
for (const attachment of attachments) {
@ -669,9 +669,9 @@ class ComprehensiveBoardMigration {
/**
* Get migration status for a board
*/
getMigrationStatus(boardId) {
async getMigrationStatus(boardId) {
try {
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
return { status: 'board_not_found' };
}
@ -684,8 +684,8 @@ class ComprehensiveBoardMigration {
};
}
const needsMigration = this.needsMigration(boardId);
const issues = this.detectMigrationIssues(boardId);
const needsMigration = await this.needsMigration(boardId);
const issues = await this.detectMigrationIssues(boardId);
return {
status: needsMigration ? 'needed' : 'not_needed',
@ -705,25 +705,25 @@ export const comprehensiveBoardMigration = new ComprehensiveBoardMigration();
// Meteor methods
Meteor.methods({
'comprehensiveBoardMigration.check'(boardId) {
async 'comprehensiveBoardMigration.check'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
return comprehensiveBoardMigration.getMigrationStatus(boardId);
return await comprehensiveBoardMigration.getMigrationStatus(boardId);
},
'comprehensiveBoardMigration.execute'(boardId) {
async 'comprehensiveBoardMigration.execute'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
const user = ReactiveCache.getUser(this.userId);
const board = ReactiveCache.getBoard(boardId);
const user = await ReactiveCache.getUser(this.userId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
throw new Meteor.Error('board-not-found');
}
@ -735,52 +735,52 @@ Meteor.methods({
throw new Meteor.Error('not-authorized', 'You must be a board admin or instance admin to perform this action.');
}
return comprehensiveBoardMigration.executeMigration(boardId);
return await comprehensiveBoardMigration.executeMigration(boardId);
},
'comprehensiveBoardMigration.needsMigration'(boardId) {
async 'comprehensiveBoardMigration.needsMigration'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
return comprehensiveBoardMigration.needsMigration(boardId);
return await comprehensiveBoardMigration.needsMigration(boardId);
},
'comprehensiveBoardMigration.detectIssues'(boardId) {
async 'comprehensiveBoardMigration.detectIssues'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
return comprehensiveBoardMigration.detectMigrationIssues(boardId);
return await comprehensiveBoardMigration.detectMigrationIssues(boardId);
},
'comprehensiveBoardMigration.fixAvatarUrls'() {
async 'comprehensiveBoardMigration.fixAvatarUrls'() {
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
const user = ReactiveCache.getUser(this.userId);
const user = await ReactiveCache.getUser(this.userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Only instance admins can perform this action.');
}
return comprehensiveBoardMigration.fixAvatarUrls();
return await comprehensiveBoardMigration.fixAvatarUrls();
},
'comprehensiveBoardMigration.fixAttachmentUrls'() {
async 'comprehensiveBoardMigration.fixAttachmentUrls'() {
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
const user = ReactiveCache.getUser(this.userId);
const user = await ReactiveCache.getUser(this.userId);
if (!user || !user.isAdmin) {
throw new Meteor.Error('not-authorized', 'Only instance admins can perform this action.');
}
return comprehensiveBoardMigration.fixAttachmentUrls();
return await comprehensiveBoardMigration.fixAttachmentUrls();
}
});

View file

@ -26,10 +26,10 @@ class DeleteDuplicateEmptyListsMigration {
/**
* Check if migration is needed for a board
*/
needsMigration(boardId) {
async needsMigration(boardId) {
try {
const lists = ReactiveCache.getLists({ boardId });
const cards = ReactiveCache.getCards({ boardId });
const lists = await ReactiveCache.getLists({ boardId });
const cards = await ReactiveCache.getCards({ boardId });
// Check if there are any empty lists that have a duplicate with the same title containing cards
for (const list of lists) {
@ -104,10 +104,10 @@ class DeleteDuplicateEmptyListsMigration {
* Convert shared lists (lists without swimlaneId) to per-swimlane lists
*/
async convertSharedListsToPerSwimlane(boardId) {
const lists = ReactiveCache.getLists({ boardId });
const swimlanes = ReactiveCache.getSwimlanes({ boardId, archived: false });
const cards = ReactiveCache.getCards({ boardId });
const lists = await ReactiveCache.getLists({ boardId });
const swimlanes = await ReactiveCache.getSwimlanes({ boardId, archived: false });
const cards = await ReactiveCache.getCards({ boardId });
let listsConverted = 0;
// Find shared lists (lists without swimlaneId)
@ -206,9 +206,9 @@ class DeleteDuplicateEmptyListsMigration {
* 3. Have a duplicate list with the same title on the same board that contains cards
*/
async deleteEmptyPerSwimlaneLists(boardId) {
const lists = ReactiveCache.getLists({ boardId });
const cards = ReactiveCache.getCards({ boardId });
const lists = await ReactiveCache.getLists({ boardId });
const cards = await ReactiveCache.getCards({ boardId });
let listsDeleted = 0;
for (const list of lists) {
@ -268,8 +268,8 @@ class DeleteDuplicateEmptyListsMigration {
* Get detailed status of empty lists
*/
async getStatus(boardId) {
const lists = ReactiveCache.getLists({ boardId });
const cards = ReactiveCache.getCards({ boardId });
const lists = await ReactiveCache.getLists({ boardId });
const cards = await ReactiveCache.getCards({ boardId });
const sharedLists = [];
const emptyPerSwimlaneLists = [];
@ -319,17 +319,17 @@ const deleteDuplicateEmptyListsMigration = new DeleteDuplicateEmptyListsMigratio
// Register Meteor methods
Meteor.methods({
'deleteDuplicateEmptyLists.needsMigration'(boardId) {
async 'deleteDuplicateEmptyLists.needsMigration'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in');
}
return deleteDuplicateEmptyListsMigration.needsMigration(boardId);
return await deleteDuplicateEmptyListsMigration.needsMigration(boardId);
},
'deleteDuplicateEmptyLists.execute'(boardId) {
async 'deleteDuplicateEmptyLists.execute'(boardId) {
check(boardId, String);
if (!this.userId) {
@ -337,12 +337,12 @@ Meteor.methods({
}
// Check if user is board admin
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
throw new Meteor.Error('board-not-found', 'Board not found');
}
const user = ReactiveCache.getUser(this.userId);
const user = await ReactiveCache.getUser(this.userId);
if (!user) {
throw new Meteor.Error('user-not-found', 'User not found');
}
@ -356,17 +356,17 @@ Meteor.methods({
throw new Meteor.Error('not-authorized', 'Only board administrators can run migrations');
}
return deleteDuplicateEmptyListsMigration.executeMigration(boardId);
return await deleteDuplicateEmptyListsMigration.executeMigration(boardId);
},
'deleteDuplicateEmptyLists.getStatus'(boardId) {
async 'deleteDuplicateEmptyLists.getStatus'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in');
}
return deleteDuplicateEmptyListsMigration.getStatus(boardId);
return await deleteDuplicateEmptyListsMigration.getStatus(boardId);
}
});

View file

@ -22,9 +22,9 @@ class FixAllFileUrlsMigration {
/**
* Check if migration is needed for a board
*/
needsMigration(boardId) {
async needsMigration(boardId) {
// Get all users who are members of this board
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board || !board.members) {
return false;
}
@ -32,7 +32,7 @@ class FixAllFileUrlsMigration {
const memberIds = board.members.map(m => m.userId);
// Check for problematic avatar URLs for board members
const users = ReactiveCache.getUsers({ _id: { $in: memberIds } });
const users = await ReactiveCache.getUsers({ _id: { $in: memberIds } });
for (const user of users) {
if (user.profile && user.profile.avatarUrl) {
const avatarUrl = user.profile.avatarUrl;
@ -43,10 +43,10 @@ class FixAllFileUrlsMigration {
}
// Check for problematic attachment URLs on this board
const cards = ReactiveCache.getCards({ boardId });
const cards = await ReactiveCache.getCards({ boardId });
const cardIds = cards.map(c => c._id);
const attachments = ReactiveCache.getAttachments({ cardId: { $in: cardIds } });
const attachments = await ReactiveCache.getAttachments({ cardId: { $in: cardIds } });
for (const attachment of attachments) {
if (attachment.url && this.hasProblematicUrl(attachment.url)) {
return true;
@ -133,13 +133,13 @@ class FixAllFileUrlsMigration {
* Fix avatar URLs in user profiles for board members
*/
async fixAvatarUrls(boardId) {
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board || !board.members) {
return 0;
}
const memberIds = board.members.map(m => m.userId);
const users = ReactiveCache.getUsers({ _id: { $in: memberIds } });
const users = await ReactiveCache.getUsers({ _id: { $in: memberIds } });
let avatarsFixed = 0;
for (const user of users) {
@ -189,9 +189,9 @@ class FixAllFileUrlsMigration {
* Fix attachment URLs in attachment records for this board
*/
async fixAttachmentUrls(boardId) {
const cards = ReactiveCache.getCards({ boardId });
const cards = await ReactiveCache.getCards({ boardId });
const cardIds = cards.map(c => c._id);
const attachments = ReactiveCache.getAttachments({ cardId: { $in: cardIds } });
const attachments = await ReactiveCache.getAttachments({ cardId: { $in: cardIds } });
let attachmentsFixed = 0;
for (const attachment of attachments) {
@ -229,9 +229,9 @@ class FixAllFileUrlsMigration {
* Fix attachment URLs in the Attachments collection for this board
*/
async fixCardAttachmentUrls(boardId) {
const cards = ReactiveCache.getCards({ boardId });
const cards = await ReactiveCache.getCards({ boardId });
const cardIds = cards.map(c => c._id);
const attachments = ReactiveCache.getAttachments({ cardId: { $in: cardIds } });
const attachments = await ReactiveCache.getAttachments({ cardId: { $in: cardIds } });
let attachmentsFixed = 0;
for (const attachment of attachments) {
@ -270,7 +270,7 @@ export const fixAllFileUrlsMigration = new FixAllFileUrlsMigration();
// Meteor methods
Meteor.methods({
'fixAllFileUrls.execute'(boardId) {
async 'fixAllFileUrls.execute'(boardId) {
check(boardId, String);
if (!this.userId) {
@ -278,12 +278,12 @@ Meteor.methods({
}
// Check if user is board admin
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
throw new Meteor.Error('board-not-found', 'Board not found');
}
const user = ReactiveCache.getUser(this.userId);
const user = await ReactiveCache.getUser(this.userId);
if (!user) {
throw new Meteor.Error('user-not-found', 'User not found');
}
@ -297,16 +297,16 @@ Meteor.methods({
throw new Meteor.Error('not-authorized', 'Only board administrators can run migrations');
}
return fixAllFileUrlsMigration.execute(boardId);
return await fixAllFileUrlsMigration.execute(boardId);
},
'fixAllFileUrls.needsMigration'(boardId) {
async 'fixAllFileUrls.needsMigration'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in');
}
return fixAllFileUrlsMigration.needsMigration(boardId);
return await fixAllFileUrlsMigration.needsMigration(boardId);
}
});

View file

@ -19,16 +19,16 @@ class FixAvatarUrlsMigration {
/**
* Check if migration is needed for a board
*/
needsMigration(boardId) {
async needsMigration(boardId) {
// Get all users who are members of this board
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board || !board.members) {
return false;
}
const memberIds = board.members.map(m => m.userId);
const users = ReactiveCache.getUsers({ _id: { $in: memberIds } });
const users = await ReactiveCache.getUsers({ _id: { $in: memberIds } });
for (const user of users) {
if (user.profile && user.profile.avatarUrl) {
const avatarUrl = user.profile.avatarUrl;
@ -46,7 +46,7 @@ class FixAvatarUrlsMigration {
*/
async execute(boardId) {
// Get all users who are members of this board
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board || !board.members) {
return {
success: false,
@ -55,7 +55,7 @@ class FixAvatarUrlsMigration {
}
const memberIds = board.members.map(m => m.userId);
const users = ReactiveCache.getUsers({ _id: { $in: memberIds } });
const users = await ReactiveCache.getUsers({ _id: { $in: memberIds } });
let avatarsFixed = 0;
console.log(`Starting avatar URL fix migration for board ${boardId}...`);
@ -131,7 +131,7 @@ export const fixAvatarUrlsMigration = new FixAvatarUrlsMigration();
// Meteor method
Meteor.methods({
'fixAvatarUrls.execute'(boardId) {
async 'fixAvatarUrls.execute'(boardId) {
check(boardId, String);
if (!this.userId) {
@ -139,12 +139,12 @@ Meteor.methods({
}
// Check if user is board admin
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
throw new Meteor.Error('board-not-found', 'Board not found');
}
const user = ReactiveCache.getUser(this.userId);
const user = await ReactiveCache.getUser(this.userId);
if (!user) {
throw new Meteor.Error('user-not-found', 'User not found');
}
@ -158,16 +158,16 @@ Meteor.methods({
throw new Meteor.Error('not-authorized', 'Only board administrators can run migrations');
}
return fixAvatarUrlsMigration.execute(boardId);
return await fixAvatarUrlsMigration.execute(boardId);
},
'fixAvatarUrls.needsMigration'(boardId) {
async 'fixAvatarUrls.needsMigration'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in');
}
return fixAvatarUrlsMigration.needsMigration(boardId);
return await fixAvatarUrlsMigration.needsMigration(boardId);
}
});

View file

@ -31,9 +31,9 @@ class FixMissingListsMigration {
/**
* Check if migration is needed for a board
*/
needsMigration(boardId) {
async needsMigration(boardId) {
try {
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) return false;
// Check if board has already been processed
@ -42,9 +42,9 @@ class FixMissingListsMigration {
}
// Check if there are cards with mismatched listId/swimlaneId
const cards = ReactiveCache.getCards({ boardId });
const lists = ReactiveCache.getLists({ boardId });
const cards = await ReactiveCache.getCards({ boardId });
const lists = await ReactiveCache.getLists({ boardId });
// Create a map of listId -> swimlaneId for existing lists
const listSwimlaneMap = new Map();
lists.forEach(list => {
@ -78,14 +78,14 @@ class FixMissingListsMigration {
console.log(`Starting fix missing lists migration for board ${boardId}`);
}
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
throw new Error(`Board ${boardId} not found`);
}
const cards = ReactiveCache.getCards({ boardId });
const lists = ReactiveCache.getLists({ boardId });
const swimlanes = ReactiveCache.getSwimlanes({ boardId });
const cards = await ReactiveCache.getCards({ boardId });
const lists = await ReactiveCache.getLists({ boardId });
const swimlanes = await ReactiveCache.getSwimlanes({ boardId });
// Create maps for efficient lookup
const listSwimlaneMap = new Map();
@ -214,9 +214,9 @@ class FixMissingListsMigration {
/**
* Get migration status for a board
*/
getMigrationStatus(boardId) {
async getMigrationStatus(boardId) {
try {
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
return { status: 'board_not_found' };
}
@ -228,7 +228,7 @@ class FixMissingListsMigration {
};
}
const needsMigration = this.needsMigration(boardId);
const needsMigration = await this.needsMigration(boardId);
return {
status: needsMigration ? 'needed' : 'not_needed'
};
@ -245,33 +245,33 @@ export const fixMissingListsMigration = new FixMissingListsMigration();
// Meteor methods
Meteor.methods({
'fixMissingListsMigration.check'(boardId) {
async 'fixMissingListsMigration.check'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
return fixMissingListsMigration.getMigrationStatus(boardId);
return await fixMissingListsMigration.getMigrationStatus(boardId);
},
'fixMissingListsMigration.execute'(boardId) {
async 'fixMissingListsMigration.execute'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
return fixMissingListsMigration.executeMigration(boardId);
return await fixMissingListsMigration.executeMigration(boardId);
},
'fixMissingListsMigration.needsMigration'(boardId) {
async 'fixMissingListsMigration.needsMigration'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized');
}
return fixMissingListsMigration.needsMigration(boardId);
return await fixMissingListsMigration.needsMigration(boardId);
}
});

View file

@ -14,7 +14,7 @@ if (Meteor.isServer) {
* @param {string} attachmentId - The old attachment ID
* @returns {Object} - Migration result
*/
migrateAttachment(attachmentId) {
async migrateAttachment(attachmentId) {
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
@ -27,7 +27,7 @@ if (Meteor.isServer) {
}
// Check if already migrated
const existingAttachment = ReactiveCache.getAttachment(attachmentId);
const existingAttachment = await ReactiveCache.getAttachment(attachmentId);
if (existingAttachment) {
return { success: true, message: 'Already migrated', attachmentId };
}
@ -72,7 +72,7 @@ if (Meteor.isServer) {
* @param {string} cardId - The card ID
* @returns {Object} - Migration results
*/
migrateCardAttachments(cardId) {
async migrateCardAttachments(cardId) {
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
@ -85,7 +85,7 @@ if (Meteor.isServer) {
try {
// Get all old attachments for this card
const oldAttachments = ReactiveCache.getAttachments({ 'meta.cardId': cardId });
const oldAttachments = await ReactiveCache.getAttachments({ 'meta.cardId': cardId });
for (const attachment of oldAttachments) {
const result = Meteor.call('migrateAttachment', attachment._id);
@ -113,14 +113,14 @@ if (Meteor.isServer) {
* @param {string} cardId - The card ID (optional)
* @returns {Object} - Migration status
*/
getAttachmentMigrationStatus(cardId) {
async getAttachmentMigrationStatus(cardId) {
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'Must be logged in');
}
try {
const selector = cardId ? { 'meta.cardId': cardId } : {};
const allAttachments = ReactiveCache.getAttachments(selector);
const allAttachments = await ReactiveCache.getAttachments(selector);
const status = {
total: allAttachments.length,

View file

@ -24,11 +24,11 @@ class RestoreAllArchivedMigration {
/**
* Check if migration is needed for a board
*/
needsMigration(boardId) {
async needsMigration(boardId) {
try {
const archivedSwimlanes = ReactiveCache.getSwimlanes({ boardId, archived: true });
const archivedLists = ReactiveCache.getLists({ boardId, archived: true });
const archivedCards = ReactiveCache.getCards({ boardId, archived: true });
const archivedSwimlanes = await ReactiveCache.getSwimlanes({ boardId, archived: true });
const archivedLists = await ReactiveCache.getLists({ boardId, archived: true });
const archivedCards = await ReactiveCache.getCards({ boardId, archived: true });
return archivedSwimlanes.length > 0 || archivedLists.length > 0 || archivedCards.length > 0;
} catch (error) {
@ -50,19 +50,19 @@ class RestoreAllArchivedMigration {
errors: []
};
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
throw new Error('Board not found');
}
// Get archived items
const archivedSwimlanes = ReactiveCache.getSwimlanes({ boardId, archived: true });
const archivedLists = ReactiveCache.getLists({ boardId, archived: true });
const archivedCards = ReactiveCache.getCards({ boardId, archived: true });
const archivedSwimlanes = await ReactiveCache.getSwimlanes({ boardId, archived: true });
const archivedLists = await ReactiveCache.getLists({ boardId, archived: true });
const archivedCards = await ReactiveCache.getCards({ boardId, archived: true });
// Get active items for reference
const activeSwimlanes = ReactiveCache.getSwimlanes({ boardId, archived: false });
const activeLists = ReactiveCache.getLists({ boardId, archived: false });
const activeSwimlanes = await ReactiveCache.getSwimlanes({ boardId, archived: false });
const activeLists = await ReactiveCache.getLists({ boardId, archived: false });
// Restore all archived swimlanes
for (const swimlane of archivedSwimlanes) {
@ -101,7 +101,7 @@ class RestoreAllArchivedMigration {
updatedAt: new Date(),
archived: false
});
targetSwimlane = ReactiveCache.getSwimlane(swimlaneId);
targetSwimlane = await ReactiveCache.getSwimlane(swimlaneId);
}
updateFields.swimlaneId = targetSwimlane._id;
@ -123,8 +123,8 @@ class RestoreAllArchivedMigration {
}
// Refresh lists after restoration
const allLists = ReactiveCache.getLists({ boardId, archived: false });
const allSwimlanes = ReactiveCache.getSwimlanes({ boardId, archived: false });
const allLists = await ReactiveCache.getLists({ boardId, archived: false });
const allSwimlanes = await ReactiveCache.getSwimlanes({ boardId, archived: false });
// Restore all archived cards and fix missing IDs
for (const card of archivedCards) {
@ -153,7 +153,7 @@ class RestoreAllArchivedMigration {
updatedAt: new Date(),
archived: false
});
targetList = ReactiveCache.getList(listId);
targetList = await ReactiveCache.getList(listId);
}
updateFields.listId = targetList._id;
@ -222,17 +222,17 @@ const restoreAllArchivedMigration = new RestoreAllArchivedMigration();
// Register Meteor methods
Meteor.methods({
'restoreAllArchived.needsMigration'(boardId) {
async 'restoreAllArchived.needsMigration'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in');
}
return restoreAllArchivedMigration.needsMigration(boardId);
return await restoreAllArchivedMigration.needsMigration(boardId);
},
'restoreAllArchived.execute'(boardId) {
async 'restoreAllArchived.execute'(boardId) {
check(boardId, String);
if (!this.userId) {
@ -240,12 +240,12 @@ Meteor.methods({
}
// Check if user is board admin
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
throw new Meteor.Error('board-not-found', 'Board not found');
}
const user = ReactiveCache.getUser(this.userId);
const user = await ReactiveCache.getUser(this.userId);
if (!user) {
throw new Meteor.Error('user-not-found', 'User not found');
}
@ -259,7 +259,7 @@ Meteor.methods({
throw new Meteor.Error('not-authorized', 'Only board administrators can run migrations');
}
return restoreAllArchivedMigration.executeMigration(boardId);
return await restoreAllArchivedMigration.executeMigration(boardId);
}
});

View file

@ -24,10 +24,10 @@ class RestoreLostCardsMigration {
/**
* Check if migration is needed for a board
*/
needsMigration(boardId) {
async needsMigration(boardId) {
try {
const cards = ReactiveCache.getCards({ boardId, archived: false });
const lists = ReactiveCache.getLists({ boardId, archived: false });
const cards = await ReactiveCache.getCards({ boardId, archived: false });
const lists = await ReactiveCache.getLists({ boardId, archived: false });
// Check for cards missing swimlaneId or listId
const lostCards = cards.filter(card => !card.swimlaneId || !card.listId);
@ -70,15 +70,15 @@ class RestoreLostCardsMigration {
errors: []
};
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
throw new Error('Board not found');
}
// Get all non-archived items
const cards = ReactiveCache.getCards({ boardId, archived: false });
const lists = ReactiveCache.getLists({ boardId, archived: false });
const swimlanes = ReactiveCache.getSwimlanes({ boardId, archived: false });
const cards = await ReactiveCache.getCards({ boardId, archived: false });
const lists = await ReactiveCache.getLists({ boardId, archived: false });
const swimlanes = await ReactiveCache.getSwimlanes({ boardId, archived: false });
// Detect items to restore BEFORE creating anything
const lostLists = lists.filter(list => !list.swimlaneId);
@ -116,7 +116,7 @@ class RestoreLostCardsMigration {
updatedAt: new Date(),
archived: false
});
lostCardsSwimlane = ReactiveCache.getSwimlane(swimlaneId);
lostCardsSwimlane = await ReactiveCache.getSwimlane(swimlaneId);
results.lostCardsSwimlaneCreated = true;
if (process.env.DEBUG === 'true') {
console.log(`Created "Lost Cards" swimlane for board ${boardId}`);
@ -156,7 +156,7 @@ class RestoreLostCardsMigration {
updatedAt: new Date(),
archived: false
});
defaultList = ReactiveCache.getList(listId);
defaultList = await ReactiveCache.getList(listId);
if (process.env.DEBUG === 'true') {
console.log(`Created default list in Lost Cards swimlane`);
}
@ -215,17 +215,17 @@ const restoreLostCardsMigration = new RestoreLostCardsMigration();
// Register Meteor methods
Meteor.methods({
'restoreLostCards.needsMigration'(boardId) {
async 'restoreLostCards.needsMigration'(boardId) {
check(boardId, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You must be logged in');
}
return restoreLostCardsMigration.needsMigration(boardId);
return await restoreLostCardsMigration.needsMigration(boardId);
},
'restoreLostCards.execute'(boardId) {
async 'restoreLostCards.execute'(boardId) {
check(boardId, String);
if (!this.userId) {
@ -233,12 +233,12 @@ Meteor.methods({
}
// Check if user is board admin
const board = ReactiveCache.getBoard(boardId);
const board = await ReactiveCache.getBoard(boardId);
if (!board) {
throw new Meteor.Error('board-not-found', 'Board not found');
}
const user = ReactiveCache.getUser(this.userId);
const user = await ReactiveCache.getUser(this.userId);
if (!user) {
throw new Meteor.Error('user-not-found', 'User not found');
}
@ -252,7 +252,7 @@ Meteor.methods({
throw new Meteor.Error('not-authorized', 'Only board administrators can run migrations');
}
return restoreLostCardsMigration.executeMigration(boardId);
return await restoreLostCardsMigration.executeMigration(boardId);
}
});

View file

@ -33,8 +33,8 @@ Meteor.startup(() => {
// Meteor.setTimeout(func, delay) does not accept args :-(
// so we pass userId with closure
const userId = user._id;
Meteor.setTimeout(() => {
const user = ReactiveCache.getUser(userId);
Meteor.setTimeout(async () => {
const user = await ReactiveCache.getUser(userId);
// for each user, in the timed period, only the first call will get the cached content,
// other calls will get nothing

View file

@ -19,12 +19,12 @@ Notifications = {
delete notifyServices[serviceName];
},
getUsers: watchers => {
getUsers: async watchers => {
const users = [];
watchers.forEach(userId => {
const user = ReactiveCache.getUser(userId);
for (const userId of watchers) {
const user = await ReactiveCache.getUser(userId);
if (user && user._id) users.push(user);
});
}
return users;
},

Some files were not shown because too many files have changed in this diff Show more