summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorRomulus Tsai 蔡仲明 <urakagi@gmail.com>2020-05-08 10:13:11 +0800
committerRomulus Tsai 蔡仲明 <urakagi@gmail.com>2020-05-08 10:13:11 +0800
commitc3458855bdb52c976ee6689ad5a0d4e92e96f2e3 (patch)
treed9dbbcc3087b5bfc520710b5f5624a3f4e2b78e6 /server
parent444848876759173ad80203129250d2f0311f30fc (diff)
parentcfcc73724fcd394150d1b815d0a7a4c466e216b5 (diff)
downloadwekan-c3458855bdb52c976ee6689ad5a0d4e92e96f2e3.tar.gz
wekan-c3458855bdb52c976ee6689ad5a0d4e92e96f2e3.tar.bz2
wekan-c3458855bdb52c976ee6689ad5a0d4e92e96f2e3.zip
Merge branch 'master' into lib-change
Diffstat (limited to 'server')
-rw-r--r--server/card-opened-webhook.js4
-rw-r--r--server/migrations.js11
-rw-r--r--server/notifications/outgoing.js361
-rw-r--r--server/notifications/profile.js10
-rw-r--r--server/publications/boards.js8
-rw-r--r--server/publications/notifications.js101
-rw-r--r--server/publications/users.js1
-rw-r--r--server/scroll.js15
-rw-r--r--server/statistics.js138
9 files changed, 393 insertions, 256 deletions
diff --git a/server/card-opened-webhook.js b/server/card-opened-webhook.js
index 242ae7ca..3c94d104 100644
--- a/server/card-opened-webhook.js
+++ b/server/card-opened-webhook.js
@@ -1,5 +1,7 @@
Meteor.startup(() => {
- if (process.env.CARD_OPENED_WEBHOOK_ENABLED) {
+ if (process.env.CARD_OPENED_WEBHOOK_ENABLED === 'true') {
Meteor.settings.public.CARD_OPENED_WEBHOOK_ENABLED = true;
+ } else {
+ Meteor.settings.public.CARD_OPENED_WEBHOOK_ENABLED = false;
}
});
diff --git a/server/migrations.js b/server/migrations.js
index b02ca246..e330f043 100644
--- a/server/migrations.js
+++ b/server/migrations.js
@@ -1034,10 +1034,13 @@ Migrations.add('add-description-text-allowed', () => {
);
});
-Migrations.add('fix-incorrect-dates', () => {
- cas = CFSAttachments.find();
- console.log('cas', cas);
-});
+Migrations.add('add-sort-field-to-boards', () => {
+ Boards.find().forEach((board, index) => {
+ if (!board.hasOwnProperty('sort')) {
+ Boards.direct.update(board._id, { $set: { sort: index } }, noValidate);
+ }
+ });
+});
import { MongoInternals } from 'meteor/mongo';
diff --git a/server/notifications/outgoing.js b/server/notifications/outgoing.js
index 5bc2c540..9a741ea1 100644
--- a/server/notifications/outgoing.js
+++ b/server/notifications/outgoing.js
@@ -1,192 +1,199 @@
-const postCatchError = Meteor.wrapAsync((url, options, resolve) => {
- HTTP.post(url, options, (err, res) => {
- if (err) {
- resolve(null, err.response);
- } else {
- resolve(null, res);
- }
+if (Meteor.isServer) {
+ const postCatchError = Meteor.wrapAsync((url, options, resolve) => {
+ HTTP.post(url, options, (err, res) => {
+ if (err) {
+ resolve(null, err.response);
+ } else {
+ resolve(null, res);
+ }
+ });
});
-});
-const Lock = {
- _lock: {},
- _timer: {},
- echoDelay: 500, // echo should be happening much faster
- normalDelay: 1e3, // normally user typed comment will be much slower
- ECHO: 2,
- NORMAL: 1,
- NULL: 0,
- has(id, value) {
- const existing = this._lock[id];
- let ret = this.NULL;
- if (existing) {
- ret = existing === value ? this.ECHO : this.NORMAL;
- }
- return ret;
- },
- clear(id, delay) {
- const previous = this._timer[id];
- if (previous) {
- Meteor.clearTimeout(previous);
- }
- this._timer[id] = Meteor.setTimeout(() => this.unset(id), delay);
- },
- set(id, value) {
- const state = this.has(id, value);
- let delay = this.normalDelay;
- if (state === this.ECHO) {
- delay = this.echoDelay;
- }
- if (!value) {
- // user commented, we set a lock
- value = 1;
- }
- this._lock[id] = value;
- this.clear(id, delay); // always auto reset the locker after delay
- },
- unset(id) {
- delete this._lock[id];
- },
-};
+ const Lock = {
+ _lock: {},
+ _timer: {},
+ echoDelay: 500, // echo should be happening much faster
+ normalDelay: 1e3, // normally user typed comment will be much slower
+ ECHO: 2,
+ NORMAL: 1,
+ NULL: 0,
+ has(id, value) {
+ const existing = this._lock[id];
+ let ret = this.NULL;
+ if (existing) {
+ ret = existing === value ? this.ECHO : this.NORMAL;
+ }
+ return ret;
+ },
+ clear(id, delay) {
+ const previous = this._timer[id];
+ if (previous) {
+ Meteor.clearTimeout(previous);
+ }
+ this._timer[id] = Meteor.setTimeout(() => this.unset(id), delay);
+ },
+ set(id, value) {
+ const state = this.has(id, value);
+ let delay = this.normalDelay;
+ if (state === this.ECHO) {
+ delay = this.echoDelay;
+ }
+ if (!value) {
+ // user commented, we set a lock
+ value = 1;
+ }
+ this._lock[id] = value;
+ this.clear(id, delay); // always auto reset the locker after delay
+ },
+ unset(id) {
+ delete this._lock[id];
+ },
+ };
-const webhooksAtbts = (process.env.WEBHOOKS_ATTRIBUTES &&
- process.env.WEBHOOKS_ATTRIBUTES.split(',')) || [
- 'cardId',
- 'listId',
- 'oldListId',
- 'boardId',
- 'comment',
- 'user',
- 'card',
- 'commentId',
- 'swimlaneId',
-];
-const responseFunc = data => {
- const paramCommentId = data.commentId;
- const paramCardId = data.cardId;
- const paramBoardId = data.boardId;
- const newComment = data.comment;
- if (paramCardId && paramBoardId && newComment) {
- // only process data with the cardid, boardid and comment text, TODO can expand other functions here to react on returned data
- const comment = CardComments.findOne({
- _id: paramCommentId,
- cardId: paramCardId,
- boardId: paramBoardId,
- });
- const board = Boards.findOne(paramBoardId);
- const card = Cards.findOne(paramCardId);
- if (board && card) {
- if (comment) {
- Lock.set(comment._id, newComment);
- CardComments.direct.update(comment._id, {
- $set: {
+ const webhooksAtbts = (process.env.WEBHOOKS_ATTRIBUTES &&
+ process.env.WEBHOOKS_ATTRIBUTES.split(',')) || [
+ 'cardId',
+ 'listId',
+ 'oldListId',
+ 'boardId',
+ 'comment',
+ 'user',
+ 'card',
+ 'commentId',
+ 'swimlaneId',
+ ];
+ const responseFunc = data => {
+ const paramCommentId = data.commentId;
+ const paramCardId = data.cardId;
+ const paramBoardId = data.boardId;
+ const newComment = data.comment;
+ if (paramCardId && paramBoardId && newComment) {
+ // only process data with the cardid, boardid and comment text, TODO can expand other functions here to react on returned data
+ const comment = CardComments.findOne({
+ _id: paramCommentId,
+ cardId: paramCardId,
+ boardId: paramBoardId,
+ });
+ const board = Boards.findOne(paramBoardId);
+ const card = Cards.findOne(paramCardId);
+ if (board && card) {
+ if (comment) {
+ Lock.set(comment._id, newComment);
+ CardComments.direct.update(comment._id, {
+ $set: {
+ text: newComment,
+ },
+ });
+ }
+ } else {
+ const userId = data.userId;
+ if (userId) {
+ const inserted = CardComments.direct.insert({
text: newComment,
- },
- });
- }
- } else {
- const userId = data.userId;
- if (userId) {
- const inserted = CardComments.direct.insert({
- text: newComment,
- userId,
- cardId,
- boardId,
- });
- Lock.set(inserted._id, newComment);
+ userId,
+ cardId,
+ boardId,
+ });
+ Lock.set(inserted._id, newComment);
+ }
}
}
- }
-};
-Meteor.methods({
- outgoingWebhooks(integration, description, params) {
- check(integration, Object);
- check(description, String);
- check(params, Object);
- this.unblock();
+ };
+ Meteor.methods({
+ outgoingWebhooks(integration, description, params) {
+ if (Meteor.user()) {
+ check(integration, Object);
+ check(description, String);
+ check(params, Object);
+ this.unblock();
- // label activity did not work yet, see wekan/models/activities.js
- const quoteParams = _.clone(params);
- const clonedParams = _.clone(params);
- [
- 'card',
- 'list',
- 'oldList',
- 'board',
- 'oldBoard',
- 'comment',
- 'checklist',
- 'swimlane',
- 'oldSwimlane',
- 'label',
- 'attachment',
- ].forEach(key => {
- if (quoteParams[key]) quoteParams[key] = `"${params[key]}"`;
- });
+ // label activity did not work yet, see wekan/models/activities.js
+ const quoteParams = _.clone(params);
+ const clonedParams = _.clone(params);
+ [
+ 'card',
+ 'list',
+ 'oldList',
+ 'board',
+ 'oldBoard',
+ 'comment',
+ 'checklist',
+ 'swimlane',
+ 'oldSwimlane',
+ 'label',
+ 'attachment',
+ ].forEach(key => {
+ if (quoteParams[key]) quoteParams[key] = `"${params[key]}"`;
+ });
- const userId = params.userId ? params.userId : integrations[0].userId;
- const user = Users.findOne(userId);
- const text = `${params.user} ${TAPi18n.__(
- description,
- quoteParams,
- user.getLanguage(),
- )}\n${params.url}`;
+ const userId = params.userId ? params.userId : integrations[0].userId;
+ const user = Users.findOne(userId);
+ const text = `${params.user} ${TAPi18n.__(
+ description,
+ quoteParams,
+ user.getLanguage(),
+ )}\n${params.url}`;
- if (text.length === 0) return;
+ if (text.length === 0) return;
- const value = {
- text: `${text}`,
- };
+ const value = {
+ text: `${text}`,
+ };
- webhooksAtbts.forEach(key => {
- if (params[key]) value[key] = params[key];
- });
- value.description = description;
- //integrations.forEach(integration => {
- const is2way = integration.type === Integrations.Const.TWOWAY;
- const token = integration.token || '';
- const headers = {
- 'Content-Type': 'application/json',
- };
- if (token) headers['X-Wekan-Token'] = token;
- const options = {
- headers,
- data: is2way ? { description, ...clonedParams } : value,
- };
- const url = integration.url;
- if (is2way) {
- const cid = params.commentId;
- const comment = params.comment;
- const lockState = cid && Lock.has(cid, comment);
- if (cid && lockState !== Lock.NULL) {
- // it's a comment and there is a previous lock
- return;
- } else if (cid) {
- Lock.set(cid, comment); // set a lock here
- }
- }
- const response = postCatchError(url, options);
+ webhooksAtbts.forEach(key => {
+ if (params[key]) value[key] = params[key];
+ });
+ value.description = description;
+ //integrations.forEach(integration => {
+ const is2way = integration.type === Integrations.Const.TWOWAY;
+ const token = integration.token || '';
+ const headers = {
+ 'Content-Type': 'application/json',
+ };
+ if (token) headers['X-Wekan-Token'] = token;
+ const options = {
+ headers,
+ data: is2way ? { description, ...clonedParams } : value,
+ };
- if (
- response &&
- response.statusCode &&
- response.statusCode >= 200 &&
- response.statusCode < 300
- ) {
- if (is2way) {
- const data = response.data; // only an JSON encoded response will be actioned
- if (data) {
- try {
- responseFunc(data);
- } catch (e) {
- throw new Meteor.Error('error-process-data');
+ if (!Integrations.findOne({ url: integration.url })) return;
+
+ const url = integration.url;
+
+ if (is2way) {
+ const cid = params.commentId;
+ const comment = params.comment;
+ const lockState = cid && Lock.has(cid, comment);
+ if (cid && lockState !== Lock.NULL) {
+ // it's a comment and there is a previous lock
+ return;
+ } else if (cid) {
+ Lock.set(cid, comment); // set a lock here
}
}
+ const response = postCatchError(url, options);
+
+ if (
+ response &&
+ response.statusCode &&
+ response.statusCode >= 200 &&
+ response.statusCode < 300
+ ) {
+ if (is2way) {
+ const data = response.data; // only an JSON encoded response will be actioned
+ if (data) {
+ try {
+ responseFunc(data);
+ } catch (e) {
+ throw new Meteor.Error('error-process-data');
+ }
+ }
+ }
+ return response; // eslint-disable-line consistent-return
+ } else {
+ throw new Meteor.Error('error-invalid-webhook-response');
+ }
}
- return response; // eslint-disable-line consistent-return
- } else {
- throw new Meteor.Error('error-invalid-webhook-response');
- }
- //});
- },
-});
+ },
+ });
+}
diff --git a/server/notifications/profile.js b/server/notifications/profile.js
index 6d9c7018..608931cf 100644
--- a/server/notifications/profile.js
+++ b/server/notifications/profile.js
@@ -1,9 +1,5 @@
Meteor.startup(() => {
- // XXX: add activity id to profile.notifications,
- // it can be displayed and rendered on web or mobile UI
- // will uncomment the following code once UI implemented
- //
- // Notifications.subscribe('profile', (user, title, description, params) => {
- // user.addNotification(params.activityId);
- // });
+ Notifications.subscribe('profile', (user, title, description, params) => {
+ user.addNotification(params.activityId);
+ });
});
diff --git a/server/publications/boards.js b/server/publications/boards.js
index 79e578b8..ae82d28c 100644
--- a/server/publications/boards.js
+++ b/server/publications/boards.js
@@ -18,7 +18,7 @@ Meteor.publish('boards', function() {
archived: false,
$or: [
{
- _id: { $in: starredBoards },
+ // _id: { $in: starredBoards }, // Commented out, to get a list of all public boards
permission: 'public',
},
{ members: { $elemMatch: { userId, isActive: true } } },
@@ -35,7 +35,9 @@ Meteor.publish('boards', function() {
members: 1,
permission: 1,
type: 1,
+ sort: 1,
},
+ sort: { sort: 1 /* boards default sorting */ },
},
);
});
@@ -61,6 +63,7 @@ Meteor.publish('archivedBoards', function() {
slug: 1,
title: 1,
},
+ sort: { sort: 1 /* boards default sorting */ },
},
);
});
@@ -90,7 +93,7 @@ Meteor.publishRelations('board', function(boardId, isArchived) {
$or,
// Sort required to ensure oplog usage
},
- { limit: 1, sort: { _id: 1 } },
+ { limit: 1, sort: { sort: 1 /* boards default sorting */ } },
),
function(boardId, board) {
this.cursor(Lists.find({ boardId, archived: isArchived }));
@@ -192,6 +195,7 @@ Meteor.publishRelations('board', function(boardId, isArchived) {
username: 1,
'profile.fullname': 1,
'profile.avatarUrl': 1,
+ 'profile.initials': 1,
},
},
),
diff --git a/server/publications/notifications.js b/server/publications/notifications.js
new file mode 100644
index 00000000..bc55a37c
--- /dev/null
+++ b/server/publications/notifications.js
@@ -0,0 +1,101 @@
+// We use these when displaying notifications in the notificationsDrawer
+
+// gets all activities associated with the current user
+Meteor.publish('notificationActivities', () => {
+ return activities();
+});
+
+// gets all attachments associated with activities associated with the current user
+Meteor.publish('notificationAttachments', function() {
+ return Attachments.find({
+ _id: {
+ $in: activities()
+ .map(v => v.attachmentId)
+ .filter(v => !!v),
+ },
+ });
+});
+
+// gets all cards associated with activities associated with the current user
+Meteor.publish('notificationCards', function() {
+ return Cards.find({
+ _id: {
+ $in: activities()
+ .map(v => v.cardId)
+ .filter(v => !!v),
+ },
+ });
+});
+
+// gets all checklistItems associated with activities associated with the current user
+Meteor.publish('notificationChecklistItems', function() {
+ return ChecklistItems.find({
+ _id: {
+ $in: activities()
+ .map(v => v.checklistItemId)
+ .filter(v => !!v),
+ },
+ });
+});
+
+// gets all checklists associated with activities associated with the current user
+Meteor.publish('notificationChecklists', function() {
+ return Checklists.find({
+ _id: {
+ $in: activities()
+ .map(v => v.checklistId)
+ .filter(v => !!v),
+ },
+ });
+});
+
+// gets all comments associated with activities associated with the current user
+Meteor.publish('notificationComments', function() {
+ return CardComments.find({
+ _id: {
+ $in: activities()
+ .map(v => v.commentId)
+ .filter(v => !!v),
+ },
+ });
+});
+
+// gets all lists associated with activities associated with the current user
+Meteor.publish('notificationLists', function() {
+ return Lists.find({
+ _id: {
+ $in: activities()
+ .map(v => v.listId)
+ .filter(v => !!v),
+ },
+ });
+});
+
+// gets all swimlanes associated with activities associated with the current user
+Meteor.publish('notificationSwimlanes', function() {
+ return Swimlanes.find({
+ _id: {
+ $in: activities()
+ .map(v => v.swimlaneId)
+ .filter(v => !!v),
+ },
+ });
+});
+
+// gets all users associated with activities associated with the current user
+Meteor.publish('notificationUsers', function() {
+ return Users.find({
+ _id: {
+ $in: activities()
+ .map(v => v.userId)
+ .filter(v => !!v),
+ },
+ });
+});
+
+function activities() {
+ const notifications = Meteor.user().profile.notifications || [];
+ return Activities.find({
+ _id: { $in: notifications.map(v => v.activity) },
+ });
+}
diff --git a/server/publications/users.js b/server/publications/users.js
index 59411ca0..c04f8c5c 100644
--- a/server/publications/users.js
+++ b/server/publications/users.js
@@ -6,6 +6,7 @@ Meteor.publish('user-miniprofile', function(userId) {
username: 1,
'profile.fullname': 1,
'profile.avatarUrl': 1,
+ 'profile.initials': 1,
},
});
});
diff --git a/server/scroll.js b/server/scroll.js
new file mode 100644
index 00000000..c2cc797e
--- /dev/null
+++ b/server/scroll.js
@@ -0,0 +1,15 @@
+Meteor.startup(() => {
+ // Mouse Scroll Intertia, issue #2949. Integer.
+ if (process.env.SCROLLINERTIA !== '0') {
+ Meteor.settings.public.SCROLLINERTIA = process.env.SCROLLINERTIA;
+ } else {
+ Meteor.settings.public.SCROLLINERTIA = 0;
+ }
+
+ // Mouse Scroll Amount, issue #2949. "auto" or Integer.
+ if (process.env.SCROLLAMOUNT !== 'auto') {
+ Meteor.settings.public.SCROLLAMOUNT = process.env.SCROLLAMOUNT;
+ } else {
+ Meteor.settings.public.SCROLLAMOUNT = 'auto';
+ }
+});
diff --git a/server/statistics.js b/server/statistics.js
index 997fd86f..0ead840f 100644
--- a/server/statistics.js
+++ b/server/statistics.js
@@ -1,68 +1,76 @@
import { MongoInternals } from 'meteor/mongo';
-Meteor.methods({
- getStatistics() {
- const os = require('os');
- const pjson = require('/package.json');
- const statistics = {};
- let wekanVersion = pjson.version;
- wekanVersion = wekanVersion.replace('v', '');
- statistics.version = wekanVersion;
- statistics.os = {
- type: os.type(),
- platform: os.platform(),
- arch: os.arch(),
- release: os.release(),
- uptime: os.uptime(),
- loadavg: os.loadavg(),
- totalmem: os.totalmem(),
- freemem: os.freemem(),
- cpus: os.cpus(),
- };
- let nodeVersion = process.version;
- nodeVersion = nodeVersion.replace('v', '');
- statistics.process = {
- nodeVersion,
- pid: process.pid,
- uptime: process.uptime(),
- };
- // Remove beginning of Meteor release text METEOR@
- let meteorVersion = Meteor.release;
- meteorVersion = meteorVersion.replace('METEOR@', '');
- statistics.meteor = {
- meteorVersion,
- };
- // Thanks to RocketChat for MongoDB version detection !
- // https://github.com/RocketChat/Rocket.Chat/blob/develop/app/utils/server/functions/getMongoInfo.js
- let mongoVersion;
- let mongoStorageEngine;
- let mongoOplogEnabled;
- try {
- const { mongo } = MongoInternals.defaultRemoteCollectionDriver();
- oplogEnabled = Boolean(
- mongo._oplogHandle && mongo._oplogHandle.onOplogEntry,
- );
- const { version, storageEngine } = Promise.await(
- mongo.db.command({ serverStatus: 1 }),
- );
- mongoVersion = version;
- mongoStorageEngine = storageEngine.name;
- mongoOplogEnabled = oplogEnabled;
- } catch (e) {
- try {
- const { version } = Promise.await(mongo.db.command({ buildinfo: 1 }));
- mongoVersion = version;
- mongoStorageEngine = 'unknown';
- } catch (e) {
- mongoVersion = 'unknown';
- mongoStorageEngine = 'unknown';
+if (Meteor.isServer) {
+ Meteor.methods({
+ getStatistics() {
+ if (Meteor.user() && Meteor.user().isAdmin) {
+ const os = require('os');
+ const pjson = require('/package.json');
+ const statistics = {};
+ let wekanVersion = pjson.version;
+ wekanVersion = wekanVersion.replace('v', '');
+ statistics.version = wekanVersion;
+ statistics.os = {
+ type: os.type(),
+ platform: os.platform(),
+ arch: os.arch(),
+ release: os.release(),
+ uptime: os.uptime(),
+ loadavg: os.loadavg(),
+ totalmem: os.totalmem(),
+ freemem: os.freemem(),
+ cpus: os.cpus(),
+ };
+ let nodeVersion = process.version;
+ nodeVersion = nodeVersion.replace('v', '');
+ statistics.process = {
+ nodeVersion,
+ pid: process.pid,
+ uptime: process.uptime(),
+ };
+ // Remove beginning of Meteor release text METEOR@
+ let meteorVersion = Meteor.release;
+ meteorVersion = meteorVersion.replace('METEOR@', '');
+ statistics.meteor = {
+ meteorVersion,
+ };
+ // Thanks to RocketChat for MongoDB version detection !
+ // https://github.com/RocketChat/Rocket.Chat/blob/develop/app/utils/server/functions/getMongoInfo.js
+ let mongoVersion;
+ let mongoStorageEngine;
+ let mongoOplogEnabled;
+ try {
+ const { mongo } = MongoInternals.defaultRemoteCollectionDriver();
+ oplogEnabled = Boolean(
+ mongo._oplogHandle && mongo._oplogHandle.onOplogEntry,
+ );
+ const { version, storageEngine } = Promise.await(
+ mongo.db.command({ serverStatus: 1 }),
+ );
+ mongoVersion = version;
+ mongoStorageEngine = storageEngine.name;
+ mongoOplogEnabled = oplogEnabled;
+ } catch (e) {
+ try {
+ const { version } = Promise.await(
+ mongo.db.command({ buildinfo: 1 }),
+ );
+ mongoVersion = version;
+ mongoStorageEngine = 'unknown';
+ } catch (e) {
+ mongoVersion = 'unknown';
+ mongoStorageEngine = 'unknown';
+ }
+ }
+ statistics.mongo = {
+ mongoVersion,
+ mongoStorageEngine,
+ mongoOplogEnabled,
+ };
+ return statistics;
+ } else {
+ return false;
}
- }
- statistics.mongo = {
- mongoVersion,
- mongoStorageEngine,
- mongoOplogEnabled,
- };
- return statistics;
- },
-});
+ },
+ });
+}