mirror of
https://github.com/wekan/wekan.git
synced 2026-02-20 23:14:07 +01:00
Merge pull request #6139 from harryadel/feature/reactive-cache-async-migration
Migrate @wekanteam/meteor-reactive-cache
This commit is contained in:
commit
1d910b8a89
121 changed files with 8748 additions and 2075 deletions
1
.meteorignore
Normal file
1
.meteorignore
Normal file
|
|
@ -0,0 +1 @@
|
|||
npm-packages/
|
||||
187
METEOR3_MIGRATION.md
Normal file
187
METEOR3_MIGRATION.md
Normal 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
|
|
@ -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;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
232
models/boards.js
232
models/boards.js
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
647
models/cards.js
647
models/cards.js
File diff suppressed because it is too large
Load diff
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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] },
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 } },
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
129
models/lists.js
129
models/lists.js
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
},
|
||||
|
|
|
|||
258
models/users.js
258
models/users.js
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
8
npm-packages/meteor-reactive-cache/.gitignore
vendored
Normal file
8
npm-packages/meteor-reactive-cache/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
*.swp
|
||||
*~
|
||||
*.iml
|
||||
.*.haste_cache.*
|
||||
.DS_Store
|
||||
.idea
|
||||
npm-debug.log
|
||||
node_modules
|
||||
22
npm-packages/meteor-reactive-cache/.travis.yml
Normal file
22
npm-packages/meteor-reactive-cache/.travis.yml
Normal 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
|
||||
50
npm-packages/meteor-reactive-cache/CHANGELOG.md
Normal file
50
npm-packages/meteor-reactive-cache/CHANGELOG.md
Normal 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.
|
||||
21
npm-packages/meteor-reactive-cache/LICENSE
Normal file
21
npm-packages/meteor-reactive-cache/LICENSE
Normal 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.
|
||||
72
npm-packages/meteor-reactive-cache/README.md
Normal file
72
npm-packages/meteor-reactive-cache/README.md
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# meteor-reactive-cache [](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))
|
||||
14
npm-packages/meteor-reactive-cache/RELEASE.md
Normal file
14
npm-packages/meteor-reactive-cache/RELEASE.md
Normal 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
|
||||
```
|
||||
92
npm-packages/meteor-reactive-cache/dist/DataCache.js
vendored
Normal file
92
npm-packages/meteor-reactive-cache/dist/DataCache.js
vendored
Normal 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;
|
||||
}
|
||||
}]);
|
||||
}();
|
||||
84
npm-packages/meteor-reactive-cache/dist/ReactiveCache.js
vendored
Normal file
84
npm-packages/meteor-reactive-cache/dist/ReactiveCache.js
vendored
Normal 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;
|
||||
}
|
||||
}]);
|
||||
}();
|
||||
27
npm-packages/meteor-reactive-cache/dist/index.js
vendored
Normal file
27
npm-packages/meteor-reactive-cache/dist/index.js
vendored
Normal 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 }; }
|
||||
9
npm-packages/meteor-reactive-cache/dist/meteor/bindEnvironment.js
vendored
Normal file
9
npm-packages/meteor-reactive-cache/dist/meteor/bindEnvironment.js
vendored
Normal 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);
|
||||
8
npm-packages/meteor-reactive-cache/dist/meteor/ejson.js
vendored
Normal file
8
npm-packages/meteor-reactive-cache/dist/meteor/ejson.js
vendored
Normal 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');
|
||||
8
npm-packages/meteor-reactive-cache/dist/meteor/tracker.js
vendored
Normal file
8
npm-packages/meteor-reactive-cache/dist/meteor/tracker.js
vendored
Normal 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');
|
||||
28
npm-packages/meteor-reactive-cache/dist/reactiveField.js
vendored
Normal file
28
npm-packages/meteor-reactive-cache/dist/reactiveField.js
vendored
Normal 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;
|
||||
};
|
||||
5321
npm-packages/meteor-reactive-cache/package-lock.json
generated
Normal file
5321
npm-packages/meteor-reactive-cache/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
63
npm-packages/meteor-reactive-cache/package.json
Normal file
63
npm-packages/meteor-reactive-cache/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
25
npm-packages/meteor-reactive-cache/release.sh
Executable file
25
npm-packages/meteor-reactive-cache/release.sh
Executable 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
|
||||
61
npm-packages/meteor-reactive-cache/src/DataCache.js
Normal file
61
npm-packages/meteor-reactive-cache/src/DataCache.js
Normal 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;
|
||||
}
|
||||
}
|
||||
56
npm-packages/meteor-reactive-cache/src/ReactiveCache.js
Normal file
56
npm-packages/meteor-reactive-cache/src/ReactiveCache.js
Normal 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;
|
||||
}
|
||||
}
|
||||
3
npm-packages/meteor-reactive-cache/src/index.js
Normal file
3
npm-packages/meteor-reactive-cache/src/index.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export { default as DataCache } from './DataCache';
|
||||
export { default as ReactiveCache } from './ReactiveCache';
|
||||
export { default as reactiveField } from './reactiveField';
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import { getGlobal } from '@wekanteam/meteor-globals';
|
||||
|
||||
const Meteor = getGlobal('meteor', 'Meteor');
|
||||
export default Meteor.bindEnvironment.bind(Meteor);
|
||||
3
npm-packages/meteor-reactive-cache/src/meteor/ejson.js
Normal file
3
npm-packages/meteor-reactive-cache/src/meteor/ejson.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { getGlobal } from '@wekanteam/meteor-globals';
|
||||
|
||||
export default getGlobal('ejson', 'EJSON');
|
||||
3
npm-packages/meteor-reactive-cache/src/meteor/tracker.js
Normal file
3
npm-packages/meteor-reactive-cache/src/meteor/tracker.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
import { getGlobal } from '@wekanteam/meteor-globals';
|
||||
|
||||
export default getGlobal('tracker', 'Tracker');
|
||||
9
npm-packages/meteor-reactive-cache/src/reactiveField.js
Normal file
9
npm-packages/meteor-reactive-cache/src/reactiveField.js
Normal 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;
|
||||
};
|
||||
0
npm-packages/meteor-reactive-cache/tests/.gitignore
vendored
Normal file
0
npm-packages/meteor-reactive-cache/tests/.gitignore
vendored
Normal 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
|
||||
1
npm-packages/meteor-reactive-cache/tests/.meteor/.gitignore
vendored
Normal file
1
npm-packages/meteor-reactive-cache/tests/.meteor/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
local
|
||||
7
npm-packages/meteor-reactive-cache/tests/.meteor/.id
Normal file
7
npm-packages/meteor-reactive-cache/tests/.meteor/.id
Normal 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
|
||||
18
npm-packages/meteor-reactive-cache/tests/.meteor/packages
Normal file
18
npm-packages/meteor-reactive-cache/tests/.meteor/packages
Normal 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
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
server
|
||||
browser
|
||||
1
npm-packages/meteor-reactive-cache/tests/.meteor/release
Normal file
1
npm-packages/meteor-reactive-cache/tests/.meteor/release
Normal file
|
|
@ -0,0 +1 @@
|
|||
METEOR@1.8.0.1
|
||||
63
npm-packages/meteor-reactive-cache/tests/.meteor/versions
Normal file
63
npm-packages/meteor-reactive-cache/tests/.meteor/versions
Normal 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
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
40
npm-packages/meteor-reactive-cache/tests/package.json
Normal file
40
npm-packages/meteor-reactive-cache/tests/package.json
Normal 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
4
package-lock.json
generated
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Add table
Add a link
Reference in a new issue