mirror of
https://github.com/wekan/wekan.git
synced 2025-12-18 00:10:13 +01:00
Merge branch 'justinr1234-created-modified' into meteor-1.8
This commit is contained in:
commit
a0a482aa8e
28 changed files with 3403 additions and 2175 deletions
|
|
@ -8,3 +8,20 @@ end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.{js,html}]
|
||||||
|
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_brace_style = 1TBS
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
max_line_length = 80
|
||||||
|
quote_type = auto
|
||||||
|
spaces_around_operators = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
|
||||||
1
.eslintignore
Normal file
1
.eslintignore
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
packages/*
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
{
|
{
|
||||||
"extends": "eslint:recommended",
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:meteor/recommended",
|
||||||
|
"prettier",
|
||||||
|
"prettier/standard"
|
||||||
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"es6": true,
|
"es6": true,
|
||||||
"node": true,
|
"node": true,
|
||||||
"browser": true
|
"browser": true,
|
||||||
|
"meteor": true
|
||||||
},
|
},
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"ecmaVersion": 2017,
|
"ecmaVersion": 2017,
|
||||||
|
|
@ -28,11 +34,12 @@
|
||||||
"no-unneeded-ternary": 2,
|
"no-unneeded-ternary": 2,
|
||||||
"radix": 2,
|
"radix": 2,
|
||||||
"semi": [2, "always"],
|
"semi": [2, "always"],
|
||||||
"camelcase": [2, {"properties": "never"}],
|
"camelcase": [2, { "properties": "never" }],
|
||||||
"comma-spacing": 2,
|
"comma-spacing": 2,
|
||||||
"comma-style": 2,
|
"comma-style": 2,
|
||||||
"eol-last": 2,
|
"eol-last": 2,
|
||||||
"linebreak-style": [2, "unix"],
|
"linebreak-style": [2, "unix"],
|
||||||
|
"meteor/audit-argument-checks": 0,
|
||||||
"new-parens": 2,
|
"new-parens": 2,
|
||||||
"no-lonely-if": 2,
|
"no-lonely-if": 2,
|
||||||
"no-multiple-empty-lines": 2,
|
"no-multiple-empty-lines": 2,
|
||||||
|
|
@ -52,8 +59,26 @@
|
||||||
"prefer-const": 2,
|
"prefer-const": 2,
|
||||||
"prefer-spread": 2,
|
"prefer-spread": 2,
|
||||||
"prefer-template": 2,
|
"prefer-template": 2,
|
||||||
"no-unused-vars" : "warn"
|
"no-unused-vars": "warn",
|
||||||
|
"prettier/prettier": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
|
"settings": {
|
||||||
|
"import/resolver": {
|
||||||
|
"meteor": {
|
||||||
|
"extensions": [".js", ".jsx"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": ["prettier", "meteor"],
|
||||||
"globals": {
|
"globals": {
|
||||||
"Meteor": false,
|
"Meteor": false,
|
||||||
"Session": false,
|
"Session": false,
|
||||||
|
|
@ -100,7 +125,7 @@
|
||||||
"Attachments": true,
|
"Attachments": true,
|
||||||
"Boards": true,
|
"Boards": true,
|
||||||
"CardComments": true,
|
"CardComments": true,
|
||||||
"DatePicker" : true,
|
"DatePicker": true,
|
||||||
"Cards": true,
|
"Cards": true,
|
||||||
"CustomFields": true,
|
"CustomFields": true,
|
||||||
"Lists": true,
|
"Lists": true,
|
||||||
|
|
|
||||||
7
.prettierignore
Normal file
7
.prettierignore
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
packages/
|
||||||
|
node_modules/
|
||||||
|
.build/
|
||||||
|
.meteor/
|
||||||
|
.vscode/
|
||||||
|
.tx/
|
||||||
|
.github/
|
||||||
8
.prettierrc
Normal file
8
.prettierrc
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"printWidth": 80,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
||||||
|
|
@ -1,18 +1,44 @@
|
||||||
AccountSettings = new Mongo.Collection('accountSettings');
|
AccountSettings = new Mongo.Collection('accountSettings');
|
||||||
|
|
||||||
AccountSettings.attachSchema(new SimpleSchema({
|
AccountSettings.attachSchema(
|
||||||
_id: {
|
new SimpleSchema({
|
||||||
type: String,
|
_id: {
|
||||||
},
|
type: String,
|
||||||
booleanValue: {
|
},
|
||||||
type: Boolean,
|
booleanValue: {
|
||||||
optional: true,
|
type: Boolean,
|
||||||
},
|
optional: true,
|
||||||
sort: {
|
},
|
||||||
type: Number,
|
sort: {
|
||||||
decimal: true,
|
type: Number,
|
||||||
},
|
decimal: true,
|
||||||
}));
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: Date,
|
||||||
|
optional: true,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modifiedAt: {
|
||||||
|
type: Date,
|
||||||
|
denyUpdate: false,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert || this.isUpsert || this.isUpdate) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
AccountSettings.allow({
|
AccountSettings.allow({
|
||||||
update(userId) {
|
update(userId) {
|
||||||
|
|
@ -21,19 +47,33 @@ AccountSettings.allow({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AccountSettings.before.update((userId, doc, fieldNames, modifier, options) => {
|
||||||
|
modifier.$set = modifier.$set || {};
|
||||||
|
modifier.$set.modifiedAt = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
AccountSettings.upsert({_id: 'accounts-allowEmailChange'}, {
|
AccountSettings._collection._ensureIndex({ modifiedAt: -1 });
|
||||||
$setOnInsert: {
|
AccountSettings.upsert(
|
||||||
booleanValue: false,
|
{ _id: 'accounts-allowEmailChange' },
|
||||||
sort: 0,
|
{
|
||||||
},
|
$setOnInsert: {
|
||||||
});
|
booleanValue: false,
|
||||||
AccountSettings.upsert({_id: 'accounts-allowUserNameChange'}, {
|
sort: 0,
|
||||||
$setOnInsert: {
|
},
|
||||||
booleanValue: false,
|
}
|
||||||
sort: 1,
|
);
|
||||||
},
|
AccountSettings.upsert(
|
||||||
});
|
{ _id: 'accounts-allowUserNameChange' },
|
||||||
|
{
|
||||||
|
$setOnInsert: {
|
||||||
|
booleanValue: false,
|
||||||
|
sort: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default AccountSettings;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
|
||||||
Actions = new Mongo.Collection('actions');
|
Actions = new Mongo.Collection('actions');
|
||||||
|
|
||||||
Actions.allow({
|
Actions.allow({
|
||||||
|
|
@ -17,3 +19,16 @@ Actions.helpers({
|
||||||
return this.desc;
|
return this.desc;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Actions.before.update((userId, doc, fieldNames, modifier, options) => {
|
||||||
|
modifier.$set = modifier.$set || {};
|
||||||
|
modifier.$set.modifiedAt = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Meteor.isServer) {
|
||||||
|
Meteor.startup(() => {
|
||||||
|
Actions._collection._ensureIndex({ modifiedAt: -1 });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Actions;
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,11 @@ Activities.before.insert((userId, doc) => {
|
||||||
Activities.after.insert((userId, doc) => {
|
Activities.after.insert((userId, doc) => {
|
||||||
const activity = Activities._transform(doc);
|
const activity = Activities._transform(doc);
|
||||||
RulesHelper.executeRules(activity);
|
RulesHelper.executeRules(activity);
|
||||||
|
});
|
||||||
|
|
||||||
|
Activities.before.update((userId, doc, fieldNames, modifier, options) => {
|
||||||
|
modifier.$set = modifier.$set || {};
|
||||||
|
modifier.$set.modifiedAt = Date.now();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
|
|
@ -78,11 +82,21 @@ if (Meteor.isServer) {
|
||||||
// are largely used in the App. See #524.
|
// are largely used in the App. See #524.
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
Activities._collection._ensureIndex({ createdAt: -1 });
|
Activities._collection._ensureIndex({ createdAt: -1 });
|
||||||
|
Activities._collection._ensureIndex({ modifiedAt: -1 });
|
||||||
Activities._collection._ensureIndex({ cardId: 1, createdAt: -1 });
|
Activities._collection._ensureIndex({ cardId: 1, createdAt: -1 });
|
||||||
Activities._collection._ensureIndex({ boardId: 1, createdAt: -1 });
|
Activities._collection._ensureIndex({ boardId: 1, createdAt: -1 });
|
||||||
Activities._collection._ensureIndex({ commentId: 1 }, { partialFilterExpression: { commentId: { $exists: true } } });
|
Activities._collection._ensureIndex(
|
||||||
Activities._collection._ensureIndex({ attachmentId: 1 }, { partialFilterExpression: { attachmentId: { $exists: true } } });
|
{ commentId: 1 },
|
||||||
Activities._collection._ensureIndex({ customFieldId: 1 }, { partialFilterExpression: { customFieldId: { $exists: true } } });
|
{ partialFilterExpression: { commentId: { $exists: true } } }
|
||||||
|
);
|
||||||
|
Activities._collection._ensureIndex(
|
||||||
|
{ attachmentId: 1 },
|
||||||
|
{ partialFilterExpression: { attachmentId: { $exists: true } } }
|
||||||
|
);
|
||||||
|
Activities._collection._ensureIndex(
|
||||||
|
{ customFieldId: 1 },
|
||||||
|
{ partialFilterExpression: { customFieldId: { $exists: true } } }
|
||||||
|
);
|
||||||
// Label activity did not work yet, unable to edit labels when tried this.
|
// Label activity did not work yet, unable to edit labels when tried this.
|
||||||
//Activities._collection._dropIndex({ labelId: 1 }, { "indexKey": -1 });
|
//Activities._collection._dropIndex({ labelId: 1 }, { "indexKey": -1 });
|
||||||
//Activities._collection._dropIndex({ labelId: 1 }, { partialFilterExpression: { labelId: { $exists: true } } });
|
//Activities._collection._dropIndex({ labelId: 1 }, { partialFilterExpression: { labelId: { $exists: true } } });
|
||||||
|
|
@ -189,18 +203,35 @@ if (Meteor.isServer) {
|
||||||
// params.labelId = activity.labelId;
|
// params.labelId = activity.labelId;
|
||||||
//}
|
//}
|
||||||
if (board) {
|
if (board) {
|
||||||
const watchingUsers = _.pluck(_.where(board.watchers, {level: 'watching'}), 'userId');
|
const watchingUsers = _.pluck(
|
||||||
const trackingUsers = _.pluck(_.where(board.watchers, {level: 'tracking'}), 'userId');
|
_.where(board.watchers, { level: 'watching' }),
|
||||||
watchers = _.union(watchers, watchingUsers, _.intersection(participants, trackingUsers));
|
'userId'
|
||||||
|
);
|
||||||
|
const trackingUsers = _.pluck(
|
||||||
|
_.where(board.watchers, { level: 'tracking' }),
|
||||||
|
'userId'
|
||||||
|
);
|
||||||
|
watchers = _.union(
|
||||||
|
watchers,
|
||||||
|
watchingUsers,
|
||||||
|
_.intersection(participants, trackingUsers)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Notifications.getUsers(watchers).forEach((user) => {
|
Notifications.getUsers(watchers).forEach((user) => {
|
||||||
Notifications.notify(user, title, description, params);
|
Notifications.notify(user, title, description, params);
|
||||||
});
|
});
|
||||||
|
|
||||||
const integrations = Integrations.find({ boardId: board._id, type: 'outgoing-webhooks', enabled: true, activities: { '$in': [description, 'all'] } }).fetch();
|
const integrations = Integrations.find({
|
||||||
|
boardId: board._id,
|
||||||
|
type: 'outgoing-webhooks',
|
||||||
|
enabled: true,
|
||||||
|
activities: { $in: [description, 'all'] },
|
||||||
|
}).fetch();
|
||||||
if (integrations.length > 0) {
|
if (integrations.length > 0) {
|
||||||
Meteor.call('outgoingWebhooks', integrations, description, params);
|
Meteor.call('outgoingWebhooks', integrations, description, params);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Activities;
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,49 @@
|
||||||
Announcements = new Mongo.Collection('announcements');
|
Announcements = new Mongo.Collection('announcements');
|
||||||
|
|
||||||
Announcements.attachSchema(new SimpleSchema({
|
Announcements.attachSchema(
|
||||||
enabled: {
|
new SimpleSchema({
|
||||||
type: Boolean,
|
enabled: {
|
||||||
defaultValue: false,
|
type: Boolean,
|
||||||
},
|
defaultValue: false,
|
||||||
title: {
|
},
|
||||||
type: String,
|
title: {
|
||||||
optional: true,
|
type: String,
|
||||||
},
|
optional: true,
|
||||||
body: {
|
},
|
||||||
type: String,
|
body: {
|
||||||
optional: true,
|
type: String,
|
||||||
},
|
optional: true,
|
||||||
sort: {
|
},
|
||||||
type: Number,
|
sort: {
|
||||||
decimal: true,
|
type: Number,
|
||||||
},
|
decimal: true,
|
||||||
}));
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: Date,
|
||||||
|
optional: true,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modifiedAt: {
|
||||||
|
type: Date,
|
||||||
|
denyUpdate: false,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert || this.isUpsert || this.isUpdate) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
Announcements.allow({
|
Announcements.allow({
|
||||||
update(userId) {
|
update(userId) {
|
||||||
|
|
@ -26,11 +52,19 @@ Announcements.allow({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Announcements.before.update((userId, doc, fieldNames, modifier, options) => {
|
||||||
|
modifier.$set = modifier.$set || {};
|
||||||
|
modifier.$set.modifiedAt = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
|
Announcements._collection._ensureIndex({ modifiedAt: -1 });
|
||||||
const announcements = Announcements.findOne({});
|
const announcements = Announcements.findOne({});
|
||||||
if(!announcements){
|
if (!announcements) {
|
||||||
Announcements.insert({enabled: false, sort: 0});
|
Announcements.insert({ enabled: false, sort: 0 });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Announcements;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
Attachments = new FS.Collection('attachments', {
|
Attachments = new FS.Collection('attachments', {
|
||||||
stores: [
|
stores: [
|
||||||
|
|
||||||
// XXX Add a new store for cover thumbnails so we don't load big images in
|
// XXX Add a new store for cover thumbnails so we don't load big images in
|
||||||
// the general board view
|
// the general board view
|
||||||
new FS.Store.GridFS('attachments', {
|
new FS.Store.GridFS('attachments', {
|
||||||
|
|
@ -25,7 +24,6 @@ Attachments = new FS.Collection('attachments', {
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
Attachments.files._ensureIndex({ cardId: 1 });
|
Attachments.files._ensureIndex({ cardId: 1 });
|
||||||
|
|
@ -78,13 +76,16 @@ if (Meteor.isServer) {
|
||||||
} else {
|
} else {
|
||||||
// Don't add activity about adding the attachment as the activity
|
// Don't add activity about adding the attachment as the activity
|
||||||
// be imported and delete source field
|
// be imported and delete source field
|
||||||
Attachments.update({
|
Attachments.update(
|
||||||
_id: doc._id,
|
{
|
||||||
}, {
|
_id: doc._id,
|
||||||
$unset: {
|
|
||||||
source: '',
|
|
||||||
},
|
},
|
||||||
});
|
{
|
||||||
|
$unset: {
|
||||||
|
source: '',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -107,3 +108,5 @@ if (Meteor.isServer) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Attachments;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
Avatars = new FS.Collection('avatars', {
|
Avatars = new FS.Collection('avatars', {
|
||||||
stores: [
|
stores: [new FS.Store.GridFS('avatars')],
|
||||||
new FS.Store.GridFS('avatars'),
|
|
||||||
],
|
|
||||||
filter: {
|
filter: {
|
||||||
maxSize: 72000,
|
maxSize: 72000,
|
||||||
allow: {
|
allow: {
|
||||||
|
|
@ -18,10 +16,14 @@ Avatars.allow({
|
||||||
insert: isOwner,
|
insert: isOwner,
|
||||||
update: isOwner,
|
update: isOwner,
|
||||||
remove: isOwner,
|
remove: isOwner,
|
||||||
download() { return true; },
|
download() {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
fetch: ['userId'],
|
fetch: ['userId'],
|
||||||
});
|
});
|
||||||
|
|
||||||
Avatars.files.before.insert((userId, doc) => {
|
Avatars.files.before.insert((userId, doc) => {
|
||||||
doc.userId = userId;
|
doc.userId = userId;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default Avatars;
|
||||||
|
|
|
||||||
953
models/boards.js
953
models/boards.js
File diff suppressed because it is too large
Load diff
|
|
@ -3,55 +3,69 @@ CardComments = new Mongo.Collection('card_comments');
|
||||||
/**
|
/**
|
||||||
* A comment on a card
|
* A comment on a card
|
||||||
*/
|
*/
|
||||||
CardComments.attachSchema(new SimpleSchema({
|
CardComments.attachSchema(
|
||||||
boardId: {
|
new SimpleSchema({
|
||||||
/**
|
boardId: {
|
||||||
* the board ID
|
/**
|
||||||
*/
|
* the board ID
|
||||||
type: String,
|
*/
|
||||||
},
|
type: String,
|
||||||
cardId: {
|
|
||||||
/**
|
|
||||||
* the card ID
|
|
||||||
*/
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
// XXX Rename in `content`? `text` is a bit vague...
|
|
||||||
text: {
|
|
||||||
/**
|
|
||||||
* the text of the comment
|
|
||||||
*/
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
// XXX We probably don't need this information here, since we already have it
|
|
||||||
// in the associated comment creation activity
|
|
||||||
createdAt: {
|
|
||||||
/**
|
|
||||||
* when was the comment created
|
|
||||||
*/
|
|
||||||
type: Date,
|
|
||||||
denyUpdate: false,
|
|
||||||
autoValue() { // eslint-disable-line consistent-return
|
|
||||||
if (this.isInsert) {
|
|
||||||
return new Date();
|
|
||||||
} else {
|
|
||||||
this.unset();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
cardId: {
|
||||||
// XXX Should probably be called `authorId`
|
/**
|
||||||
userId: {
|
* the card ID
|
||||||
/**
|
*/
|
||||||
* the author ID of the comment
|
type: String,
|
||||||
*/
|
|
||||||
type: String,
|
|
||||||
autoValue() { // eslint-disable-line consistent-return
|
|
||||||
if (this.isInsert && !this.isSet) {
|
|
||||||
return this.userId;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
// XXX Rename in `content`? `text` is a bit vague...
|
||||||
}));
|
text: {
|
||||||
|
/**
|
||||||
|
* the text of the comment
|
||||||
|
*/
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
/**
|
||||||
|
* when was the comment created
|
||||||
|
*/
|
||||||
|
type: Date,
|
||||||
|
denyUpdate: false,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modifiedAt: {
|
||||||
|
type: Date,
|
||||||
|
denyUpdate: false,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert || this.isUpsert || this.isUpdate) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// XXX Should probably be called `authorId`
|
||||||
|
userId: {
|
||||||
|
/**
|
||||||
|
* the author ID of the comment
|
||||||
|
*/
|
||||||
|
type: String,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert && !this.isSet) {
|
||||||
|
return this.userId;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
CardComments.allow({
|
CardComments.allow({
|
||||||
insert(userId, doc) {
|
insert(userId, doc) {
|
||||||
|
|
@ -80,7 +94,7 @@ CardComments.helpers({
|
||||||
|
|
||||||
CardComments.hookOptions.after.update = { fetchPrevious: false };
|
CardComments.hookOptions.after.update = { fetchPrevious: false };
|
||||||
|
|
||||||
function commentCreation(userId, doc){
|
function commentCreation(userId, doc) {
|
||||||
const card = Cards.findOne(doc.cardId);
|
const card = Cards.findOne(doc.cardId);
|
||||||
Activities.insert({
|
Activities.insert({
|
||||||
userId,
|
userId,
|
||||||
|
|
@ -93,10 +107,16 @@ function commentCreation(userId, doc){
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CardComments.before.update((userId, doc, fieldNames, modifier, options) => {
|
||||||
|
modifier.$set = modifier.$set || {};
|
||||||
|
modifier.$set.modifiedAt = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
// Comments are often fetched within a card, so we create an index to make these
|
// Comments are often fetched within a card, so we create an index to make these
|
||||||
// queries more efficient.
|
// queries more efficient.
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
|
CardComments._collection._ensureIndex({ modifiedAt: -1 });
|
||||||
CardComments._collection._ensureIndex({ cardId: 1, createdAt: -1 });
|
CardComments._collection._ensureIndex({ cardId: 1, createdAt: -1 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -152,14 +172,20 @@ if (Meteor.isServer) {
|
||||||
* comment: string,
|
* comment: string,
|
||||||
* authorId: string}]
|
* authorId: string}]
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/comments', function (req, res) {
|
JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/comments', function(
|
||||||
|
req,
|
||||||
|
res
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
Authentication.checkUserId( req.userId);
|
Authentication.checkUserId(req.userId);
|
||||||
const paramBoardId = req.params.boardId;
|
const paramBoardId = req.params.boardId;
|
||||||
const paramCardId = req.params.cardId;
|
const paramCardId = req.params.cardId;
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: CardComments.find({ boardId: paramBoardId, cardId: paramCardId}).map(function (doc) {
|
data: CardComments.find({
|
||||||
|
boardId: paramBoardId,
|
||||||
|
cardId: paramCardId,
|
||||||
|
}).map(function(doc) {
|
||||||
return {
|
return {
|
||||||
_id: doc._id,
|
_id: doc._id,
|
||||||
comment: doc.text,
|
comment: doc.text,
|
||||||
|
|
@ -167,8 +193,7 @@ if (Meteor.isServer) {
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: error,
|
data: error,
|
||||||
|
|
@ -185,24 +210,31 @@ if (Meteor.isServer) {
|
||||||
* @param {string} commentId the ID of the comment to retrieve
|
* @param {string} commentId the ID of the comment to retrieve
|
||||||
* @return_type CardComments
|
* @return_type CardComments
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/comments/:commentId', function (req, res) {
|
JsonRoutes.add(
|
||||||
try {
|
'GET',
|
||||||
Authentication.checkUserId( req.userId);
|
'/api/boards/:boardId/cards/:cardId/comments/:commentId',
|
||||||
const paramBoardId = req.params.boardId;
|
function(req, res) {
|
||||||
const paramCommentId = req.params.commentId;
|
try {
|
||||||
const paramCardId = req.params.cardId;
|
Authentication.checkUserId(req.userId);
|
||||||
JsonRoutes.sendResult(res, {
|
const paramBoardId = req.params.boardId;
|
||||||
code: 200,
|
const paramCommentId = req.params.commentId;
|
||||||
data: CardComments.findOne({ _id: paramCommentId, cardId: paramCardId, boardId: paramBoardId }),
|
const paramCardId = req.params.cardId;
|
||||||
});
|
JsonRoutes.sendResult(res, {
|
||||||
|
code: 200,
|
||||||
|
data: CardComments.findOne({
|
||||||
|
_id: paramCommentId,
|
||||||
|
cardId: paramCardId,
|
||||||
|
boardId: paramBoardId,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
JsonRoutes.sendResult(res, {
|
||||||
|
code: 200,
|
||||||
|
data: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
);
|
||||||
JsonRoutes.sendResult(res, {
|
|
||||||
code: 200,
|
|
||||||
data: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @operation new_comment
|
* @operation new_comment
|
||||||
|
|
@ -214,35 +246,42 @@ if (Meteor.isServer) {
|
||||||
* @param {string} text the content of the comment
|
* @param {string} text the content of the comment
|
||||||
* @return_type {_id: string}
|
* @return_type {_id: string}
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('POST', '/api/boards/:boardId/cards/:cardId/comments', function (req, res) {
|
JsonRoutes.add(
|
||||||
try {
|
'POST',
|
||||||
Authentication.checkUserId( req.userId);
|
'/api/boards/:boardId/cards/:cardId/comments',
|
||||||
const paramBoardId = req.params.boardId;
|
function(req, res) {
|
||||||
const paramCardId = req.params.cardId;
|
try {
|
||||||
const id = CardComments.direct.insert({
|
Authentication.checkUserId(req.userId);
|
||||||
userId: req.body.authorId,
|
const paramBoardId = req.params.boardId;
|
||||||
text: req.body.comment,
|
const paramCardId = req.params.cardId;
|
||||||
cardId: paramCardId,
|
const id = CardComments.direct.insert({
|
||||||
boardId: paramBoardId,
|
userId: req.body.authorId,
|
||||||
});
|
text: req.body.comment,
|
||||||
|
cardId: paramCardId,
|
||||||
|
boardId: paramBoardId,
|
||||||
|
});
|
||||||
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: {
|
data: {
|
||||||
|
_id: id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const cardComment = CardComments.findOne({
|
||||||
_id: id,
|
_id: id,
|
||||||
},
|
cardId: paramCardId,
|
||||||
});
|
boardId: paramBoardId,
|
||||||
|
});
|
||||||
const cardComment = CardComments.findOne({_id: id, cardId:paramCardId, boardId: paramBoardId });
|
commentCreation(req.body.authorId, cardComment);
|
||||||
commentCreation(req.body.authorId, cardComment);
|
} catch (error) {
|
||||||
|
JsonRoutes.sendResult(res, {
|
||||||
|
code: 200,
|
||||||
|
data: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
);
|
||||||
JsonRoutes.sendResult(res, {
|
|
||||||
code: 200,
|
|
||||||
data: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @operation delete_comment
|
* @operation delete_comment
|
||||||
|
|
@ -253,25 +292,34 @@ if (Meteor.isServer) {
|
||||||
* @param {string} commentId the ID of the comment to delete
|
* @param {string} commentId the ID of the comment to delete
|
||||||
* @return_type {_id: string}
|
* @return_type {_id: string}
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('DELETE', '/api/boards/:boardId/cards/:cardId/comments/:commentId', function (req, res) {
|
JsonRoutes.add(
|
||||||
try {
|
'DELETE',
|
||||||
Authentication.checkUserId( req.userId);
|
'/api/boards/:boardId/cards/:cardId/comments/:commentId',
|
||||||
const paramBoardId = req.params.boardId;
|
function(req, res) {
|
||||||
const paramCommentId = req.params.commentId;
|
try {
|
||||||
const paramCardId = req.params.cardId;
|
Authentication.checkUserId(req.userId);
|
||||||
CardComments.remove({ _id: paramCommentId, cardId: paramCardId, boardId: paramBoardId });
|
const paramBoardId = req.params.boardId;
|
||||||
JsonRoutes.sendResult(res, {
|
const paramCommentId = req.params.commentId;
|
||||||
code: 200,
|
const paramCardId = req.params.cardId;
|
||||||
data: {
|
CardComments.remove({
|
||||||
_id: paramCardId,
|
_id: paramCommentId,
|
||||||
},
|
cardId: paramCardId,
|
||||||
});
|
boardId: paramBoardId,
|
||||||
|
});
|
||||||
|
JsonRoutes.sendResult(res, {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
_id: paramCardId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
JsonRoutes.sendResult(res, {
|
||||||
|
code: 200,
|
||||||
|
data: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
);
|
||||||
JsonRoutes.sendResult(res, {
|
|
||||||
code: 200,
|
|
||||||
data: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default CardComments;
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,8 @@ Cards.attachSchema(new SimpleSchema({
|
||||||
* creation date
|
* creation date
|
||||||
*/
|
*/
|
||||||
type: Date,
|
type: Date,
|
||||||
autoValue() { // eslint-disable-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
if (this.isInsert) {
|
if (this.isInsert) {
|
||||||
return new Date();
|
return new Date();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -89,6 +90,18 @@ Cards.attachSchema(new SimpleSchema({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
modifiedAt: {
|
||||||
|
type: Date,
|
||||||
|
denyUpdate: false,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert || this.isUpsert || this.isUpdate) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
customFields: {
|
customFields: {
|
||||||
/**
|
/**
|
||||||
* list of custom fields
|
* list of custom fields
|
||||||
|
|
@ -1539,7 +1552,8 @@ if (Meteor.isServer) {
|
||||||
// Cards are often fetched within a board, so we create an index to make these
|
// Cards are often fetched within a board, so we create an index to make these
|
||||||
// queries more efficient.
|
// queries more efficient.
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
Cards._collection._ensureIndex({boardId: 1, createdAt: -1});
|
Cards._collection._ensureIndex({ modifiedAt: -1 });
|
||||||
|
Cards._collection._ensureIndex({ boardId: 1, createdAt: -1 });
|
||||||
// https://github.com/wekan/wekan/issues/1863
|
// https://github.com/wekan/wekan/issues/1863
|
||||||
// Swimlane added a new field in the cards collection of mongodb named parentId.
|
// Swimlane added a new field in the cards collection of mongodb named parentId.
|
||||||
// When loading a board, mongodb is searching for every cards, the id of the parent (in the swinglanes collection).
|
// When loading a board, mongodb is searching for every cards, the id of the parent (in the swinglanes collection).
|
||||||
|
|
@ -1581,6 +1595,11 @@ if (Meteor.isServer) {
|
||||||
cardCustomFields(userId, doc, fieldNames, modifier);
|
cardCustomFields(userId, doc, fieldNames, modifier);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Cards.before.update((userId, doc, fieldNames, modifier, options) => {
|
||||||
|
modifier.$set = modifier.$set || {};
|
||||||
|
modifier.$set.modifiedAt = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
// Remove all activities associated with a card if we remove the card
|
// Remove all activities associated with a card if we remove the card
|
||||||
// Remove also card_comments / checklists / attachments
|
// Remove also card_comments / checklists / attachments
|
||||||
Cards.before.remove((userId, doc) => {
|
Cards.before.remove((userId, doc) => {
|
||||||
|
|
@ -1980,3 +1999,5 @@ if (Meteor.isServer) {
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Cards;
|
||||||
|
|
|
||||||
|
|
@ -3,40 +3,66 @@ ChecklistItems = new Mongo.Collection('checklistItems');
|
||||||
/**
|
/**
|
||||||
* An item in a checklist
|
* An item in a checklist
|
||||||
*/
|
*/
|
||||||
ChecklistItems.attachSchema(new SimpleSchema({
|
ChecklistItems.attachSchema(
|
||||||
title: {
|
new SimpleSchema({
|
||||||
/**
|
title: {
|
||||||
* the text of the item
|
/**
|
||||||
*/
|
* the text of the item
|
||||||
type: String,
|
*/
|
||||||
},
|
type: String,
|
||||||
sort: {
|
},
|
||||||
/**
|
sort: {
|
||||||
* the sorting field of the item
|
/**
|
||||||
*/
|
* the sorting field of the item
|
||||||
type: Number,
|
*/
|
||||||
decimal: true,
|
type: Number,
|
||||||
},
|
decimal: true,
|
||||||
isFinished: {
|
},
|
||||||
/**
|
isFinished: {
|
||||||
* Is the item checked?
|
/**
|
||||||
*/
|
* Is the item checked?
|
||||||
type: Boolean,
|
*/
|
||||||
defaultValue: false,
|
type: Boolean,
|
||||||
},
|
defaultValue: false,
|
||||||
checklistId: {
|
},
|
||||||
/**
|
checklistId: {
|
||||||
* the checklist ID the item is attached to
|
/**
|
||||||
*/
|
* the checklist ID the item is attached to
|
||||||
type: String,
|
*/
|
||||||
},
|
type: String,
|
||||||
cardId: {
|
},
|
||||||
/**
|
cardId: {
|
||||||
* the card ID the item is attached to
|
/**
|
||||||
*/
|
* the card ID the item is attached to
|
||||||
type: String,
|
*/
|
||||||
},
|
type: String,
|
||||||
}));
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: Date,
|
||||||
|
optional: true,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modifiedAt: {
|
||||||
|
type: Date,
|
||||||
|
denyUpdate: false,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert || this.isUpsert || this.isUpdate) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
ChecklistItems.allow({
|
ChecklistItems.allow({
|
||||||
insert(userId, doc) {
|
insert(userId, doc) {
|
||||||
|
|
@ -62,10 +88,10 @@ ChecklistItems.mutations({
|
||||||
setTitle(title) {
|
setTitle(title) {
|
||||||
return { $set: { title } };
|
return { $set: { title } };
|
||||||
},
|
},
|
||||||
check(){
|
check() {
|
||||||
return { $set: { isFinished: true } };
|
return { $set: { isFinished: true } };
|
||||||
},
|
},
|
||||||
uncheck(){
|
uncheck() {
|
||||||
return { $set: { isFinished: false } };
|
return { $set: { isFinished: false } };
|
||||||
},
|
},
|
||||||
toggleItem() {
|
toggleItem() {
|
||||||
|
|
@ -79,7 +105,7 @@ ChecklistItems.mutations({
|
||||||
sort: sortIndex,
|
sort: sortIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {$set: mutatedFields};
|
return { $set: mutatedFields };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -106,13 +132,13 @@ function itemRemover(userId, doc) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function publishCheckActivity(userId, doc){
|
function publishCheckActivity(userId, doc) {
|
||||||
const card = Cards.findOne(doc.cardId);
|
const card = Cards.findOne(doc.cardId);
|
||||||
const boardId = card.boardId;
|
const boardId = card.boardId;
|
||||||
let activityType;
|
let activityType;
|
||||||
if(doc.isFinished){
|
if (doc.isFinished) {
|
||||||
activityType = 'checkedItem';
|
activityType = 'checkedItem';
|
||||||
}else{
|
} else {
|
||||||
activityType = 'uncheckedItem';
|
activityType = 'uncheckedItem';
|
||||||
}
|
}
|
||||||
const act = {
|
const act = {
|
||||||
|
|
@ -122,19 +148,19 @@ function publishCheckActivity(userId, doc){
|
||||||
boardId,
|
boardId,
|
||||||
checklistId: doc.checklistId,
|
checklistId: doc.checklistId,
|
||||||
checklistItemId: doc._id,
|
checklistItemId: doc._id,
|
||||||
checklistItemName:doc.title,
|
checklistItemName: doc.title,
|
||||||
listId: card.listId,
|
listId: card.listId,
|
||||||
swimlaneId: card.swimlaneId,
|
swimlaneId: card.swimlaneId,
|
||||||
};
|
};
|
||||||
Activities.insert(act);
|
Activities.insert(act);
|
||||||
}
|
}
|
||||||
|
|
||||||
function publishChekListCompleted(userId, doc){
|
function publishChekListCompleted(userId, doc) {
|
||||||
const card = Cards.findOne(doc.cardId);
|
const card = Cards.findOne(doc.cardId);
|
||||||
const boardId = card.boardId;
|
const boardId = card.boardId;
|
||||||
const checklistId = doc.checklistId;
|
const checklistId = doc.checklistId;
|
||||||
const checkList = Checklists.findOne({_id:checklistId});
|
const checkList = Checklists.findOne({ _id: checklistId });
|
||||||
if(checkList.isFinished()){
|
if (checkList.isFinished()) {
|
||||||
const act = {
|
const act = {
|
||||||
userId,
|
userId,
|
||||||
activityType: 'completeChecklist',
|
activityType: 'completeChecklist',
|
||||||
|
|
@ -149,11 +175,11 @@ function publishChekListCompleted(userId, doc){
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function publishChekListUncompleted(userId, doc){
|
function publishChekListUncompleted(userId, doc) {
|
||||||
const card = Cards.findOne(doc.cardId);
|
const card = Cards.findOne(doc.cardId);
|
||||||
const boardId = card.boardId;
|
const boardId = card.boardId;
|
||||||
const checklistId = doc.checklistId;
|
const checklistId = doc.checklistId;
|
||||||
const checkList = Checklists.findOne({_id:checklistId});
|
const checkList = Checklists.findOne({ _id: checklistId });
|
||||||
// BUGS in IFTTT Rules: https://github.com/wekan/wekan/issues/1972
|
// BUGS in IFTTT Rules: https://github.com/wekan/wekan/issues/1972
|
||||||
// Currently in checklist all are set as uncompleted/not checked,
|
// Currently in checklist all are set as uncompleted/not checked,
|
||||||
// IFTTT Rule does not move card to other list.
|
// IFTTT Rule does not move card to other list.
|
||||||
|
|
@ -167,7 +193,7 @@ function publishChekListUncompleted(userId, doc){
|
||||||
// find . | xargs grep 'count' -sl | grep -v .meteor | grep -v node_modules | grep -v .build
|
// find . | xargs grep 'count' -sl | grep -v .meteor | grep -v node_modules | grep -v .build
|
||||||
// Maybe something related here?
|
// Maybe something related here?
|
||||||
// wekan/client/components/rules/triggers/checklistTriggers.js
|
// wekan/client/components/rules/triggers/checklistTriggers.js
|
||||||
if(checkList.isFinished()){
|
if (checkList.isFinished()) {
|
||||||
const act = {
|
const act = {
|
||||||
userId,
|
userId,
|
||||||
activityType: 'uncompleteChecklist',
|
activityType: 'uncompleteChecklist',
|
||||||
|
|
@ -185,6 +211,7 @@ function publishChekListUncompleted(userId, doc){
|
||||||
// Activities
|
// Activities
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
|
ChecklistItems._collection._ensureIndex({ modifiedAt: -1 });
|
||||||
ChecklistItems._collection._ensureIndex({ checklistId: 1 });
|
ChecklistItems._collection._ensureIndex({ checklistId: 1 });
|
||||||
ChecklistItems._collection._ensureIndex({ cardId: 1 });
|
ChecklistItems._collection._ensureIndex({ cardId: 1 });
|
||||||
});
|
});
|
||||||
|
|
@ -198,6 +225,10 @@ if (Meteor.isServer) {
|
||||||
publishChekListUncompleted(userId, doc, fieldNames);
|
publishChekListUncompleted(userId, doc, fieldNames);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ChecklistItems.before.update((userId, doc, fieldNames, modifier, options) => {
|
||||||
|
modifier.$set = modifier.$set || {};
|
||||||
|
modifier.$set.modifiedAt = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
ChecklistItems.after.insert((userId, doc) => {
|
ChecklistItems.after.insert((userId, doc) => {
|
||||||
itemCreation(userId, doc);
|
itemCreation(userId, doc);
|
||||||
|
|
@ -214,7 +245,7 @@ if (Meteor.isServer) {
|
||||||
boardId,
|
boardId,
|
||||||
checklistId: doc.checklistId,
|
checklistId: doc.checklistId,
|
||||||
checklistItemId: doc._id,
|
checklistItemId: doc._id,
|
||||||
checklistItemName:doc.title,
|
checklistItemName: doc.title,
|
||||||
listId: card.listId,
|
listId: card.listId,
|
||||||
swimlaneId: card.swimlaneId,
|
swimlaneId: card.swimlaneId,
|
||||||
});
|
});
|
||||||
|
|
@ -233,21 +264,25 @@ if (Meteor.isServer) {
|
||||||
* @param {string} itemId the ID of the item
|
* @param {string} itemId the ID of the item
|
||||||
* @return_type ChecklistItems
|
* @return_type ChecklistItems
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId', function (req, res) {
|
JsonRoutes.add(
|
||||||
Authentication.checkUserId( req.userId);
|
'GET',
|
||||||
const paramItemId = req.params.itemId;
|
'/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId',
|
||||||
const checklistItem = ChecklistItems.findOne({ _id: paramItemId });
|
function(req, res) {
|
||||||
if (checklistItem) {
|
Authentication.checkUserId(req.userId);
|
||||||
JsonRoutes.sendResult(res, {
|
const paramItemId = req.params.itemId;
|
||||||
code: 200,
|
const checklistItem = ChecklistItems.findOne({ _id: paramItemId });
|
||||||
data: checklistItem,
|
if (checklistItem) {
|
||||||
});
|
JsonRoutes.sendResult(res, {
|
||||||
} else {
|
code: 200,
|
||||||
JsonRoutes.sendResult(res, {
|
data: checklistItem,
|
||||||
code: 500,
|
});
|
||||||
});
|
} else {
|
||||||
|
JsonRoutes.sendResult(res, {
|
||||||
|
code: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @operation edit_checklist_item
|
* @operation edit_checklist_item
|
||||||
|
|
@ -262,25 +297,35 @@ if (Meteor.isServer) {
|
||||||
* @param {string} [title] the new text of the item
|
* @param {string} [title] the new text of the item
|
||||||
* @return_type {_id: string}
|
* @return_type {_id: string}
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('PUT', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId', function (req, res) {
|
JsonRoutes.add(
|
||||||
Authentication.checkUserId( req.userId);
|
'PUT',
|
||||||
|
'/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId',
|
||||||
|
function(req, res) {
|
||||||
|
Authentication.checkUserId(req.userId);
|
||||||
|
|
||||||
const paramItemId = req.params.itemId;
|
const paramItemId = req.params.itemId;
|
||||||
|
|
||||||
if (req.body.hasOwnProperty('isFinished')) {
|
if (req.body.hasOwnProperty('isFinished')) {
|
||||||
ChecklistItems.direct.update({_id: paramItemId}, {$set: {isFinished: req.body.isFinished}});
|
ChecklistItems.direct.update(
|
||||||
|
{ _id: paramItemId },
|
||||||
|
{ $set: { isFinished: req.body.isFinished } }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (req.body.hasOwnProperty('title')) {
|
||||||
|
ChecklistItems.direct.update(
|
||||||
|
{ _id: paramItemId },
|
||||||
|
{ $set: { title: req.body.title } }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonRoutes.sendResult(res, {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
_id: paramItemId,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (req.body.hasOwnProperty('title')) {
|
);
|
||||||
ChecklistItems.direct.update({_id: paramItemId}, {$set: {title: req.body.title}});
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonRoutes.sendResult(res, {
|
|
||||||
code: 200,
|
|
||||||
data: {
|
|
||||||
_id: paramItemId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @operation delete_checklist_item
|
* @operation delete_checklist_item
|
||||||
|
|
@ -295,15 +340,21 @@ if (Meteor.isServer) {
|
||||||
* @param {string} itemId the ID of the item to be removed
|
* @param {string} itemId the ID of the item to be removed
|
||||||
* @return_type {_id: string}
|
* @return_type {_id: string}
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('DELETE', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId', function (req, res) {
|
JsonRoutes.add(
|
||||||
Authentication.checkUserId( req.userId);
|
'DELETE',
|
||||||
const paramItemId = req.params.itemId;
|
'/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId',
|
||||||
ChecklistItems.direct.remove({ _id: paramItemId });
|
function(req, res) {
|
||||||
JsonRoutes.sendResult(res, {
|
Authentication.checkUserId(req.userId);
|
||||||
code: 200,
|
const paramItemId = req.params.itemId;
|
||||||
data: {
|
ChecklistItems.direct.remove({ _id: paramItemId });
|
||||||
_id: paramItemId,
|
JsonRoutes.sendResult(res, {
|
||||||
},
|
code: 200,
|
||||||
});
|
data: {
|
||||||
});
|
_id: paramItemId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ChecklistItems;
|
||||||
|
|
|
||||||
|
|
@ -3,49 +3,64 @@ Checklists = new Mongo.Collection('checklists');
|
||||||
/**
|
/**
|
||||||
* A Checklist
|
* A Checklist
|
||||||
*/
|
*/
|
||||||
Checklists.attachSchema(new SimpleSchema({
|
Checklists.attachSchema(
|
||||||
cardId: {
|
new SimpleSchema({
|
||||||
/**
|
cardId: {
|
||||||
* The ID of the card the checklist is in
|
/**
|
||||||
*/
|
* The ID of the card the checklist is in
|
||||||
type: String,
|
*/
|
||||||
},
|
type: String,
|
||||||
title: {
|
|
||||||
/**
|
|
||||||
* the title of the checklist
|
|
||||||
*/
|
|
||||||
type: String,
|
|
||||||
defaultValue: 'Checklist',
|
|
||||||
},
|
|
||||||
finishedAt: {
|
|
||||||
/**
|
|
||||||
* When was the checklist finished
|
|
||||||
*/
|
|
||||||
type: Date,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
/**
|
|
||||||
* Creation date of the checklist
|
|
||||||
*/
|
|
||||||
type: Date,
|
|
||||||
denyUpdate: false,
|
|
||||||
autoValue() { // eslint-disable-line consistent-return
|
|
||||||
if (this.isInsert) {
|
|
||||||
return new Date();
|
|
||||||
} else {
|
|
||||||
this.unset();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
title: {
|
||||||
sort: {
|
/**
|
||||||
/**
|
* the title of the checklist
|
||||||
* sorting value of the checklist
|
*/
|
||||||
*/
|
type: String,
|
||||||
type: Number,
|
defaultValue: 'Checklist',
|
||||||
decimal: true,
|
},
|
||||||
},
|
finishedAt: {
|
||||||
}));
|
/**
|
||||||
|
* When was the checklist finished
|
||||||
|
*/
|
||||||
|
type: Date,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
/**
|
||||||
|
* Creation date of the checklist
|
||||||
|
*/
|
||||||
|
type: Date,
|
||||||
|
denyUpdate: false,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modifiedAt: {
|
||||||
|
type: Date,
|
||||||
|
denyUpdate: false,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert || this.isUpsert || this.isUpdate) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sort: {
|
||||||
|
/**
|
||||||
|
* sorting value of the checklist
|
||||||
|
*/
|
||||||
|
type: Number,
|
||||||
|
decimal: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
Checklists.helpers({
|
Checklists.helpers({
|
||||||
copy(newCardId) {
|
copy(newCardId) {
|
||||||
|
|
@ -53,7 +68,7 @@ Checklists.helpers({
|
||||||
this._id = null;
|
this._id = null;
|
||||||
this.cardId = newCardId;
|
this.cardId = newCardId;
|
||||||
const newChecklistId = Checklists.insert(this);
|
const newChecklistId = Checklists.insert(this);
|
||||||
ChecklistItems.find({checklistId: oldChecklistId}).forEach((item) => {
|
ChecklistItems.find({ checklistId: oldChecklistId }).forEach((item) => {
|
||||||
item._id = null;
|
item._id = null;
|
||||||
item.checklistId = newChecklistId;
|
item.checklistId = newChecklistId;
|
||||||
item.cardId = newCardId;
|
item.cardId = newCardId;
|
||||||
|
|
@ -65,9 +80,12 @@ Checklists.helpers({
|
||||||
return ChecklistItems.find({ checklistId: this._id }).count();
|
return ChecklistItems.find({ checklistId: this._id }).count();
|
||||||
},
|
},
|
||||||
items() {
|
items() {
|
||||||
return ChecklistItems.find({
|
return ChecklistItems.find(
|
||||||
checklistId: this._id,
|
{
|
||||||
}, { sort: ['sort'] });
|
checklistId: this._id,
|
||||||
|
},
|
||||||
|
{ sort: ['sort'] }
|
||||||
|
);
|
||||||
},
|
},
|
||||||
finishedCount() {
|
finishedCount() {
|
||||||
return ChecklistItems.find({
|
return ChecklistItems.find({
|
||||||
|
|
@ -78,20 +96,20 @@ Checklists.helpers({
|
||||||
isFinished() {
|
isFinished() {
|
||||||
return 0 !== this.itemCount() && this.itemCount() === this.finishedCount();
|
return 0 !== this.itemCount() && this.itemCount() === this.finishedCount();
|
||||||
},
|
},
|
||||||
checkAllItems(){
|
checkAllItems() {
|
||||||
const checkItems = ChecklistItems.find({checklistId: this._id});
|
const checkItems = ChecklistItems.find({ checklistId: this._id });
|
||||||
checkItems.forEach(function(item){
|
checkItems.forEach(function(item) {
|
||||||
item.check();
|
item.check();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
uncheckAllItems(){
|
uncheckAllItems() {
|
||||||
const checkItems = ChecklistItems.find({checklistId: this._id});
|
const checkItems = ChecklistItems.find({ checklistId: this._id });
|
||||||
checkItems.forEach(function(item){
|
checkItems.forEach(function(item) {
|
||||||
item.uncheck();
|
item.uncheck();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
itemIndex(itemId) {
|
itemIndex(itemId) {
|
||||||
const items = self.findOne({_id : this._id}).items;
|
const items = self.findOne({ _id: this._id }).items;
|
||||||
return _.pluck(items, '_id').indexOf(itemId);
|
return _.pluck(items, '_id').indexOf(itemId);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -124,6 +142,7 @@ Checklists.mutations({
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
|
Checklists._collection._ensureIndex({ modifiedAt: -1 });
|
||||||
Checklists._collection._ensureIndex({ cardId: 1, createdAt: 1 });
|
Checklists._collection._ensureIndex({ cardId: 1, createdAt: 1 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -135,12 +154,17 @@ if (Meteor.isServer) {
|
||||||
cardId: doc.cardId,
|
cardId: doc.cardId,
|
||||||
boardId: card.boardId,
|
boardId: card.boardId,
|
||||||
checklistId: doc._id,
|
checklistId: doc._id,
|
||||||
checklistName:doc.title,
|
checklistName: doc.title,
|
||||||
listId: card.listId,
|
listId: card.listId,
|
||||||
swimlaneId: card.swimlaneId,
|
swimlaneId: card.swimlaneId,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Checklists.before.update((userId, doc, fieldNames, modifier, options) => {
|
||||||
|
modifier.$set = modifier.$set || {};
|
||||||
|
modifier.$set.modifiedAt = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
Checklists.before.remove((userId, doc) => {
|
Checklists.before.remove((userId, doc) => {
|
||||||
const activities = Activities.find({ checklistId: doc._id });
|
const activities = Activities.find({ checklistId: doc._id });
|
||||||
const card = Cards.findOne(doc.cardId);
|
const card = Cards.findOne(doc.cardId);
|
||||||
|
|
@ -155,7 +179,7 @@ if (Meteor.isServer) {
|
||||||
cardId: doc.cardId,
|
cardId: doc.cardId,
|
||||||
boardId: Cards.findOne(doc.cardId).boardId,
|
boardId: Cards.findOne(doc.cardId).boardId,
|
||||||
checklistId: doc._id,
|
checklistId: doc._id,
|
||||||
checklistName:doc.title,
|
checklistName: doc.title,
|
||||||
listId: card.listId,
|
listId: card.listId,
|
||||||
swimlaneId: card.swimlaneId,
|
swimlaneId: card.swimlaneId,
|
||||||
});
|
});
|
||||||
|
|
@ -172,26 +196,32 @@ if (Meteor.isServer) {
|
||||||
* @return_type [{_id: string,
|
* @return_type [{_id: string,
|
||||||
* title: string}]
|
* title: string}]
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/checklists', function (req, res) {
|
JsonRoutes.add(
|
||||||
Authentication.checkUserId( req.userId);
|
'GET',
|
||||||
const paramCardId = req.params.cardId;
|
'/api/boards/:boardId/cards/:cardId/checklists',
|
||||||
const checklists = Checklists.find({ cardId: paramCardId }).map(function (doc) {
|
function(req, res) {
|
||||||
return {
|
Authentication.checkUserId(req.userId);
|
||||||
_id: doc._id,
|
const paramCardId = req.params.cardId;
|
||||||
title: doc.title,
|
const checklists = Checklists.find({ cardId: paramCardId }).map(function(
|
||||||
};
|
doc
|
||||||
});
|
) {
|
||||||
if (checklists) {
|
return {
|
||||||
JsonRoutes.sendResult(res, {
|
_id: doc._id,
|
||||||
code: 200,
|
title: doc.title,
|
||||||
data: checklists,
|
};
|
||||||
});
|
|
||||||
} else {
|
|
||||||
JsonRoutes.sendResult(res, {
|
|
||||||
code: 500,
|
|
||||||
});
|
});
|
||||||
|
if (checklists) {
|
||||||
|
JsonRoutes.sendResult(res, {
|
||||||
|
code: 200,
|
||||||
|
data: checklists,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
JsonRoutes.sendResult(res, {
|
||||||
|
code: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @operation get_checklist
|
* @operation get_checklist
|
||||||
|
|
@ -209,29 +239,38 @@ if (Meteor.isServer) {
|
||||||
* title: string,
|
* title: string,
|
||||||
* isFinished: boolean}]}
|
* isFinished: boolean}]}
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId', function (req, res) {
|
JsonRoutes.add(
|
||||||
Authentication.checkUserId( req.userId);
|
'GET',
|
||||||
const paramChecklistId = req.params.checklistId;
|
'/api/boards/:boardId/cards/:cardId/checklists/:checklistId',
|
||||||
const paramCardId = req.params.cardId;
|
function(req, res) {
|
||||||
const checklist = Checklists.findOne({ _id: paramChecklistId, cardId: paramCardId });
|
Authentication.checkUserId(req.userId);
|
||||||
if (checklist) {
|
const paramChecklistId = req.params.checklistId;
|
||||||
checklist.items = ChecklistItems.find({checklistId: checklist._id}).map(function (doc) {
|
const paramCardId = req.params.cardId;
|
||||||
return {
|
const checklist = Checklists.findOne({
|
||||||
_id: doc._id,
|
_id: paramChecklistId,
|
||||||
title: doc.title,
|
cardId: paramCardId,
|
||||||
isFinished: doc.isFinished,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
JsonRoutes.sendResult(res, {
|
|
||||||
code: 200,
|
|
||||||
data: checklist,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
JsonRoutes.sendResult(res, {
|
|
||||||
code: 500,
|
|
||||||
});
|
});
|
||||||
|
if (checklist) {
|
||||||
|
checklist.items = ChecklistItems.find({
|
||||||
|
checklistId: checklist._id,
|
||||||
|
}).map(function(doc) {
|
||||||
|
return {
|
||||||
|
_id: doc._id,
|
||||||
|
title: doc.title,
|
||||||
|
isFinished: doc.isFinished,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
JsonRoutes.sendResult(res, {
|
||||||
|
code: 200,
|
||||||
|
data: checklist,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
JsonRoutes.sendResult(res, {
|
||||||
|
code: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @operation new_checklist
|
* @operation new_checklist
|
||||||
|
|
@ -242,36 +281,40 @@ if (Meteor.isServer) {
|
||||||
* @param {string} title the title of the new checklist
|
* @param {string} title the title of the new checklist
|
||||||
* @return_type {_id: string}
|
* @return_type {_id: string}
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('POST', '/api/boards/:boardId/cards/:cardId/checklists', function (req, res) {
|
JsonRoutes.add(
|
||||||
Authentication.checkUserId( req.userId);
|
'POST',
|
||||||
|
'/api/boards/:boardId/cards/:cardId/checklists',
|
||||||
|
function(req, res) {
|
||||||
|
Authentication.checkUserId(req.userId);
|
||||||
|
|
||||||
const paramCardId = req.params.cardId;
|
const paramCardId = req.params.cardId;
|
||||||
const id = Checklists.insert({
|
const id = Checklists.insert({
|
||||||
title: req.body.title,
|
title: req.body.title,
|
||||||
cardId: paramCardId,
|
cardId: paramCardId,
|
||||||
sort: 0,
|
sort: 0,
|
||||||
});
|
});
|
||||||
if (id) {
|
if (id) {
|
||||||
req.body.items.forEach(function (item, idx) {
|
req.body.items.forEach(function(item, idx) {
|
||||||
ChecklistItems.insert({
|
ChecklistItems.insert({
|
||||||
cardId: paramCardId,
|
cardId: paramCardId,
|
||||||
checklistId: id,
|
checklistId: id,
|
||||||
title: item.title,
|
title: item.title,
|
||||||
sort: idx,
|
sort: idx,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
JsonRoutes.sendResult(res, {
|
||||||
JsonRoutes.sendResult(res, {
|
code: 200,
|
||||||
code: 200,
|
data: {
|
||||||
data: {
|
_id: id,
|
||||||
_id: id,
|
},
|
||||||
},
|
});
|
||||||
});
|
} else {
|
||||||
} else {
|
JsonRoutes.sendResult(res, {
|
||||||
JsonRoutes.sendResult(res, {
|
code: 400,
|
||||||
code: 400,
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @operation delete_checklist
|
* @operation delete_checklist
|
||||||
|
|
@ -284,15 +327,21 @@ if (Meteor.isServer) {
|
||||||
* @param {string} checklistId the ID of the checklist to remove
|
* @param {string} checklistId the ID of the checklist to remove
|
||||||
* @return_type {_id: string}
|
* @return_type {_id: string}
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('DELETE', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId', function (req, res) {
|
JsonRoutes.add(
|
||||||
Authentication.checkUserId( req.userId);
|
'DELETE',
|
||||||
const paramChecklistId = req.params.checklistId;
|
'/api/boards/:boardId/cards/:cardId/checklists/:checklistId',
|
||||||
Checklists.remove({ _id: paramChecklistId });
|
function(req, res) {
|
||||||
JsonRoutes.sendResult(res, {
|
Authentication.checkUserId(req.userId);
|
||||||
code: 200,
|
const paramChecklistId = req.params.checklistId;
|
||||||
data: {
|
Checklists.remove({ _id: paramChecklistId });
|
||||||
_id: paramChecklistId,
|
JsonRoutes.sendResult(res, {
|
||||||
},
|
code: 200,
|
||||||
});
|
data: {
|
||||||
});
|
_id: paramChecklistId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Checklists;
|
||||||
|
|
|
||||||
|
|
@ -3,74 +3,100 @@ CustomFields = new Mongo.Collection('customFields');
|
||||||
/**
|
/**
|
||||||
* A custom field on a card in the board
|
* A custom field on a card in the board
|
||||||
*/
|
*/
|
||||||
CustomFields.attachSchema(new SimpleSchema({
|
CustomFields.attachSchema(
|
||||||
boardIds: {
|
new SimpleSchema({
|
||||||
/**
|
boardIds: {
|
||||||
* the ID of the board
|
/**
|
||||||
*/
|
* the ID of the board
|
||||||
type: [String],
|
*/
|
||||||
},
|
type: [String],
|
||||||
name: {
|
},
|
||||||
/**
|
name: {
|
||||||
* name of the custom field
|
/**
|
||||||
*/
|
* name of the custom field
|
||||||
type: String,
|
*/
|
||||||
},
|
type: String,
|
||||||
type: {
|
},
|
||||||
/**
|
type: {
|
||||||
* type of the custom field
|
/**
|
||||||
*/
|
* type of the custom field
|
||||||
type: String,
|
*/
|
||||||
allowedValues: ['text', 'number', 'date', 'dropdown'],
|
type: String,
|
||||||
},
|
allowedValues: ['text', 'number', 'date', 'dropdown'],
|
||||||
settings: {
|
},
|
||||||
/**
|
settings: {
|
||||||
* settings of the custom field
|
/**
|
||||||
*/
|
* settings of the custom field
|
||||||
type: Object,
|
*/
|
||||||
},
|
type: Object,
|
||||||
'settings.dropdownItems': {
|
},
|
||||||
/**
|
'settings.dropdownItems': {
|
||||||
* list of drop down items objects
|
/**
|
||||||
*/
|
* list of drop down items objects
|
||||||
type: [Object],
|
*/
|
||||||
optional: true,
|
type: [Object],
|
||||||
},
|
optional: true,
|
||||||
'settings.dropdownItems.$': {
|
},
|
||||||
type: new SimpleSchema({
|
'settings.dropdownItems.$': {
|
||||||
_id: {
|
type: new SimpleSchema({
|
||||||
/**
|
_id: {
|
||||||
* ID of the drop down item
|
/**
|
||||||
*/
|
* ID of the drop down item
|
||||||
type: String,
|
*/
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
/**
|
||||||
|
* name of the drop down item
|
||||||
|
*/
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
showOnCard: {
|
||||||
|
/**
|
||||||
|
* should we show on the cards this custom field
|
||||||
|
*/
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
automaticallyOnCard: {
|
||||||
|
/**
|
||||||
|
* should the custom fields automatically be added on cards?
|
||||||
|
*/
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
showLabelOnMiniCard: {
|
||||||
|
/**
|
||||||
|
* should the label of the custom field be shown on minicards?
|
||||||
|
*/
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: Date,
|
||||||
|
optional: true,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
name: {
|
},
|
||||||
/**
|
modifiedAt: {
|
||||||
* name of the drop down item
|
type: Date,
|
||||||
*/
|
denyUpdate: false,
|
||||||
type: String,
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert || this.isUpsert || this.isUpdate) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
},
|
})
|
||||||
showOnCard: {
|
);
|
||||||
/**
|
|
||||||
* should we show on the cards this custom field
|
|
||||||
*/
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
automaticallyOnCard: {
|
|
||||||
/**
|
|
||||||
* should the custom fields automatically be added on cards?
|
|
||||||
*/
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
showLabelOnMiniCard: {
|
|
||||||
/**
|
|
||||||
* should the label of the custom field be shown on minicards?
|
|
||||||
*/
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
CustomFields.mutations({
|
CustomFields.mutations({
|
||||||
addBoard(boardId) {
|
addBoard(boardId) {
|
||||||
|
|
@ -88,19 +114,28 @@ CustomFields.mutations({
|
||||||
|
|
||||||
CustomFields.allow({
|
CustomFields.allow({
|
||||||
insert(userId, doc) {
|
insert(userId, doc) {
|
||||||
return allowIsAnyBoardMember(userId, Boards.find({
|
return allowIsAnyBoardMember(
|
||||||
_id: {$in: doc.boardIds},
|
userId,
|
||||||
}).fetch());
|
Boards.find({
|
||||||
|
_id: { $in: doc.boardIds },
|
||||||
|
}).fetch()
|
||||||
|
);
|
||||||
},
|
},
|
||||||
update(userId, doc) {
|
update(userId, doc) {
|
||||||
return allowIsAnyBoardMember(userId, Boards.find({
|
return allowIsAnyBoardMember(
|
||||||
_id: {$in: doc.boardIds},
|
userId,
|
||||||
}).fetch());
|
Boards.find({
|
||||||
|
_id: { $in: doc.boardIds },
|
||||||
|
}).fetch()
|
||||||
|
);
|
||||||
},
|
},
|
||||||
remove(userId, doc) {
|
remove(userId, doc) {
|
||||||
return allowIsAnyBoardMember(userId, Boards.find({
|
return allowIsAnyBoardMember(
|
||||||
_id: {$in: doc.boardIds},
|
userId,
|
||||||
}).fetch());
|
Boards.find({
|
||||||
|
_id: { $in: doc.boardIds },
|
||||||
|
}).fetch()
|
||||||
|
);
|
||||||
},
|
},
|
||||||
fetch: ['userId', 'boardIds'],
|
fetch: ['userId', 'boardIds'],
|
||||||
});
|
});
|
||||||
|
|
@ -108,7 +143,7 @@ CustomFields.allow({
|
||||||
// not sure if we need this?
|
// not sure if we need this?
|
||||||
//CustomFields.hookOptions.after.update = { fetchPrevious: false };
|
//CustomFields.hookOptions.after.update = { fetchPrevious: false };
|
||||||
|
|
||||||
function customFieldCreation(userId, doc){
|
function customFieldCreation(userId, doc) {
|
||||||
Activities.insert({
|
Activities.insert({
|
||||||
userId,
|
userId,
|
||||||
activityType: 'createCustomField',
|
activityType: 'createCustomField',
|
||||||
|
|
@ -142,6 +177,7 @@ function customFieldEdit(userId, doc){
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
|
CustomFields._collection._ensureIndex({ modifiedAt: -1 });
|
||||||
CustomFields._collection._ensureIndex({ boardIds: 1 });
|
CustomFields._collection._ensureIndex({ boardIds: 1 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -149,12 +185,17 @@ if (Meteor.isServer) {
|
||||||
customFieldCreation(userId, doc);
|
customFieldCreation(userId, doc);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
CustomFields.before.update((userId, doc, fieldNames, modifier, options) => {
|
||||||
|
modifier.$set = modifier.$set || {};
|
||||||
|
modifier.$set.modifiedAt = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
CustomFields.before.update((userId, doc, fieldNames, modifier) => {
|
CustomFields.before.update((userId, doc, fieldNames, modifier) => {
|
||||||
if (_.contains(fieldNames, 'boardIds') && modifier.$pull) {
|
if (_.contains(fieldNames, 'boardIds') && modifier.$pull) {
|
||||||
Cards.update(
|
Cards.update(
|
||||||
{boardId: modifier.$pull.boardIds, 'customFields._id': doc._id},
|
{ boardId: modifier.$pull.boardIds, 'customFields._id': doc._id },
|
||||||
{$pull: {'customFields': {'_id': doc._id}}},
|
{ $pull: { customFields: { _id: doc._id } } },
|
||||||
{multi: true}
|
{ multi: true }
|
||||||
);
|
);
|
||||||
customFieldEdit(userId, doc);
|
customFieldEdit(userId, doc);
|
||||||
Activities.remove({
|
Activities.remove({
|
||||||
|
|
@ -180,9 +221,9 @@ if (Meteor.isServer) {
|
||||||
});
|
});
|
||||||
|
|
||||||
Cards.update(
|
Cards.update(
|
||||||
{boardId: {$in: doc.boardIds}, 'customFields._id': doc._id},
|
{ boardId: { $in: doc.boardIds }, 'customFields._id': doc._id },
|
||||||
{$pull: {'customFields': {'_id': doc._id}}},
|
{ $pull: { customFields: { _id: doc._id } } },
|
||||||
{multi: true}
|
{ multi: true }
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -198,18 +239,23 @@ if (Meteor.isServer) {
|
||||||
* name: string,
|
* name: string,
|
||||||
* type: string}]
|
* type: string}]
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('GET', '/api/boards/:boardId/custom-fields', function (req, res) {
|
JsonRoutes.add('GET', '/api/boards/:boardId/custom-fields', function(
|
||||||
Authentication.checkUserId( req.userId);
|
req,
|
||||||
|
res
|
||||||
|
) {
|
||||||
|
Authentication.checkUserId(req.userId);
|
||||||
const paramBoardId = req.params.boardId;
|
const paramBoardId = req.params.boardId;
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: CustomFields.find({ boardIds: {$in: [paramBoardId]} }).map(function (cf) {
|
data: CustomFields.find({ boardIds: { $in: [paramBoardId] } }).map(
|
||||||
return {
|
function(cf) {
|
||||||
_id: cf._id,
|
return {
|
||||||
name: cf.name,
|
_id: cf._id,
|
||||||
type: cf.type,
|
name: cf.name,
|
||||||
};
|
type: cf.type,
|
||||||
}),
|
};
|
||||||
|
}
|
||||||
|
),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -221,15 +267,22 @@ if (Meteor.isServer) {
|
||||||
* @param {string} customFieldId the ID of the custom field
|
* @param {string} customFieldId the ID of the custom field
|
||||||
* @return_type CustomFields
|
* @return_type CustomFields
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('GET', '/api/boards/:boardId/custom-fields/:customFieldId', function (req, res) {
|
JsonRoutes.add(
|
||||||
Authentication.checkUserId( req.userId);
|
'GET',
|
||||||
const paramBoardId = req.params.boardId;
|
'/api/boards/:boardId/custom-fields/:customFieldId',
|
||||||
const paramCustomFieldId = req.params.customFieldId;
|
function(req, res) {
|
||||||
JsonRoutes.sendResult(res, {
|
Authentication.checkUserId(req.userId);
|
||||||
code: 200,
|
const paramBoardId = req.params.boardId;
|
||||||
data: CustomFields.findOne({ _id: paramCustomFieldId, boardIds: {$in: [paramBoardId]} }),
|
const paramCustomFieldId = req.params.customFieldId;
|
||||||
});
|
JsonRoutes.sendResult(res, {
|
||||||
});
|
code: 200,
|
||||||
|
data: CustomFields.findOne({
|
||||||
|
_id: paramCustomFieldId,
|
||||||
|
boardIds: { $in: [paramBoardId] },
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @operation new_custom_field
|
* @operation new_custom_field
|
||||||
|
|
@ -244,8 +297,11 @@ if (Meteor.isServer) {
|
||||||
* @param {boolean} showLabelOnMiniCard should the label of the custom field be shown on minicards?
|
* @param {boolean} showLabelOnMiniCard should the label of the custom field be shown on minicards?
|
||||||
* @return_type {_id: string}
|
* @return_type {_id: string}
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('POST', '/api/boards/:boardId/custom-fields', function (req, res) {
|
JsonRoutes.add('POST', '/api/boards/:boardId/custom-fields', function(
|
||||||
Authentication.checkUserId( req.userId);
|
req,
|
||||||
|
res
|
||||||
|
) {
|
||||||
|
Authentication.checkUserId(req.userId);
|
||||||
const paramBoardId = req.params.boardId;
|
const paramBoardId = req.params.boardId;
|
||||||
const id = CustomFields.direct.insert({
|
const id = CustomFields.direct.insert({
|
||||||
name: req.body.name,
|
name: req.body.name,
|
||||||
|
|
@ -254,10 +310,13 @@ if (Meteor.isServer) {
|
||||||
showOnCard: req.body.showOnCard,
|
showOnCard: req.body.showOnCard,
|
||||||
automaticallyOnCard: req.body.automaticallyOnCard,
|
automaticallyOnCard: req.body.automaticallyOnCard,
|
||||||
showLabelOnMiniCard: req.body.showLabelOnMiniCard,
|
showLabelOnMiniCard: req.body.showLabelOnMiniCard,
|
||||||
boardIds: {$in: [paramBoardId]},
|
boardIds: { $in: [paramBoardId] },
|
||||||
});
|
});
|
||||||
|
|
||||||
const customField = CustomFields.findOne({_id: id, boardIds: {$in: [paramBoardId]} });
|
const customField = CustomFields.findOne({
|
||||||
|
_id: id,
|
||||||
|
boardIds: { $in: [paramBoardId] },
|
||||||
|
});
|
||||||
customFieldCreation(req.body.authorId, customField);
|
customFieldCreation(req.body.authorId, customField);
|
||||||
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
|
|
@ -278,16 +337,22 @@ if (Meteor.isServer) {
|
||||||
* @param {string} customFieldId the ID of the custom field
|
* @param {string} customFieldId the ID of the custom field
|
||||||
* @return_type {_id: string}
|
* @return_type {_id: string}
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('DELETE', '/api/boards/:boardId/custom-fields/:customFieldId', function (req, res) {
|
JsonRoutes.add(
|
||||||
Authentication.checkUserId( req.userId);
|
'DELETE',
|
||||||
const paramBoardId = req.params.boardId;
|
'/api/boards/:boardId/custom-fields/:customFieldId',
|
||||||
const id = req.params.customFieldId;
|
function(req, res) {
|
||||||
CustomFields.remove({ _id: id, boardIds: {$in: [paramBoardId]} });
|
Authentication.checkUserId(req.userId);
|
||||||
JsonRoutes.sendResult(res, {
|
const paramBoardId = req.params.boardId;
|
||||||
code: 200,
|
const id = req.params.customFieldId;
|
||||||
data: {
|
CustomFields.remove({ _id: id, boardIds: { $in: [paramBoardId] } });
|
||||||
_id: id,
|
JsonRoutes.sendResult(res, {
|
||||||
},
|
code: 200,
|
||||||
});
|
data: {
|
||||||
});
|
_id: id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default CustomFields;
|
||||||
|
|
|
||||||
|
|
@ -3,75 +3,96 @@ Integrations = new Mongo.Collection('integrations');
|
||||||
/**
|
/**
|
||||||
* Integration with third-party applications
|
* Integration with third-party applications
|
||||||
*/
|
*/
|
||||||
Integrations.attachSchema(new SimpleSchema({
|
Integrations.attachSchema(
|
||||||
enabled: {
|
new SimpleSchema({
|
||||||
/**
|
enabled: {
|
||||||
* is the integration enabled?
|
/**
|
||||||
*/
|
* is the integration enabled?
|
||||||
type: Boolean,
|
*/
|
||||||
defaultValue: true,
|
type: Boolean,
|
||||||
},
|
defaultValue: true,
|
||||||
title: {
|
|
||||||
/**
|
|
||||||
* name of the integration
|
|
||||||
*/
|
|
||||||
type: String,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
/**
|
|
||||||
* type of the integratation (Default to 'outgoing-webhooks')
|
|
||||||
*/
|
|
||||||
type: String,
|
|
||||||
defaultValue: 'outgoing-webhooks',
|
|
||||||
},
|
|
||||||
activities: {
|
|
||||||
/**
|
|
||||||
* activities the integration gets triggered (list)
|
|
||||||
*/
|
|
||||||
type: [String],
|
|
||||||
defaultValue: ['all'],
|
|
||||||
},
|
|
||||||
url: { // URL validation regex (https://mathiasbynens.be/demo/url-regex)
|
|
||||||
/**
|
|
||||||
* URL validation regex (https://mathiasbynens.be/demo/url-regex)
|
|
||||||
*/
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
token: {
|
|
||||||
/**
|
|
||||||
* token of the integration
|
|
||||||
*/
|
|
||||||
type: String,
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
boardId: {
|
|
||||||
/**
|
|
||||||
* Board ID of the integration
|
|
||||||
*/
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
/**
|
|
||||||
* Creation date of the integration
|
|
||||||
*/
|
|
||||||
type: Date,
|
|
||||||
denyUpdate: false,
|
|
||||||
autoValue() { // eslint-disable-line consistent-return
|
|
||||||
if (this.isInsert) {
|
|
||||||
return new Date();
|
|
||||||
} else {
|
|
||||||
this.unset();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
title: {
|
||||||
userId: {
|
/**
|
||||||
/**
|
* name of the integration
|
||||||
* user ID who created the interation
|
*/
|
||||||
*/
|
type: String,
|
||||||
type: String,
|
optional: true,
|
||||||
},
|
},
|
||||||
}));
|
type: {
|
||||||
|
/**
|
||||||
|
* type of the integratation (Default to 'outgoing-webhooks')
|
||||||
|
*/
|
||||||
|
type: String,
|
||||||
|
defaultValue: 'outgoing-webhooks',
|
||||||
|
},
|
||||||
|
activities: {
|
||||||
|
/**
|
||||||
|
* activities the integration gets triggered (list)
|
||||||
|
*/
|
||||||
|
type: [String],
|
||||||
|
defaultValue: ['all'],
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
// URL validation regex (https://mathiasbynens.be/demo/url-regex)
|
||||||
|
/**
|
||||||
|
* URL validation regex (https://mathiasbynens.be/demo/url-regex)
|
||||||
|
*/
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
token: {
|
||||||
|
/**
|
||||||
|
* token of the integration
|
||||||
|
*/
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
boardId: {
|
||||||
|
/**
|
||||||
|
* Board ID of the integration
|
||||||
|
*/
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
/**
|
||||||
|
* Creation date of the integration
|
||||||
|
*/
|
||||||
|
type: Date,
|
||||||
|
denyUpdate: false,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modifiedAt: {
|
||||||
|
type: Date,
|
||||||
|
denyUpdate: false,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert || this.isUpsert || this.isUpdate) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
userId: {
|
||||||
|
/**
|
||||||
|
* user ID who created the interation
|
||||||
|
*/
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
Integrations.before.update((userId, doc, fieldNames, modifier, options) => {
|
||||||
|
modifier.$set = modifier.$set || {};
|
||||||
|
modifier.$set.modifiedAt = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
Integrations.allow({
|
Integrations.allow({
|
||||||
insert(userId, doc) {
|
insert(userId, doc) {
|
||||||
|
|
@ -89,6 +110,7 @@ Integrations.allow({
|
||||||
//INTEGRATIONS REST API
|
//INTEGRATIONS REST API
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
|
Integrations._collection._ensureIndex({ modifiedAt: -1 });
|
||||||
Integrations._collection._ensureIndex({ boardId: 1 });
|
Integrations._collection._ensureIndex({ boardId: 1 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -99,18 +121,23 @@ if (Meteor.isServer) {
|
||||||
* @param {string} boardId the board ID
|
* @param {string} boardId the board ID
|
||||||
* @return_type [Integrations]
|
* @return_type [Integrations]
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('GET', '/api/boards/:boardId/integrations', function(req, res) {
|
JsonRoutes.add('GET', '/api/boards/:boardId/integrations', function(
|
||||||
|
req,
|
||||||
|
res
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const paramBoardId = req.params.boardId;
|
const paramBoardId = req.params.boardId;
|
||||||
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
||||||
|
|
||||||
const data = Integrations.find({ boardId: paramBoardId }, { fields: { token: 0 } }).map(function(doc) {
|
const data = Integrations.find(
|
||||||
|
{ boardId: paramBoardId },
|
||||||
|
{ fields: { token: 0 } }
|
||||||
|
).map(function(doc) {
|
||||||
return doc;
|
return doc;
|
||||||
});
|
});
|
||||||
|
|
||||||
JsonRoutes.sendResult(res, {code: 200, data});
|
JsonRoutes.sendResult(res, { code: 200, data });
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: error,
|
data: error,
|
||||||
|
|
@ -126,7 +153,10 @@ if (Meteor.isServer) {
|
||||||
* @param {string} intId the integration ID
|
* @param {string} intId the integration ID
|
||||||
* @return_type Integrations
|
* @return_type Integrations
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('GET', '/api/boards/:boardId/integrations/:intId', function(req, res) {
|
JsonRoutes.add('GET', '/api/boards/:boardId/integrations/:intId', function(
|
||||||
|
req,
|
||||||
|
res
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const paramBoardId = req.params.boardId;
|
const paramBoardId = req.params.boardId;
|
||||||
const paramIntId = req.params.intId;
|
const paramIntId = req.params.intId;
|
||||||
|
|
@ -134,10 +164,12 @@ if (Meteor.isServer) {
|
||||||
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: Integrations.findOne({ _id: paramIntId, boardId: paramBoardId }, { fields: { token: 0 } }),
|
data: Integrations.findOne(
|
||||||
|
{ _id: paramIntId, boardId: paramBoardId },
|
||||||
|
{ fields: { token: 0 } }
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: error,
|
data: error,
|
||||||
|
|
@ -153,7 +185,10 @@ if (Meteor.isServer) {
|
||||||
* @param {string} url the URL of the integration
|
* @param {string} url the URL of the integration
|
||||||
* @return_type {_id: string}
|
* @return_type {_id: string}
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('POST', '/api/boards/:boardId/integrations', function(req, res) {
|
JsonRoutes.add('POST', '/api/boards/:boardId/integrations', function(
|
||||||
|
req,
|
||||||
|
res
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const paramBoardId = req.params.boardId;
|
const paramBoardId = req.params.boardId;
|
||||||
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
||||||
|
|
@ -170,8 +205,7 @@ if (Meteor.isServer) {
|
||||||
_id: id,
|
_id: id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: error,
|
data: error,
|
||||||
|
|
@ -192,7 +226,10 @@ if (Meteor.isServer) {
|
||||||
* @param {string} [activities] new list of activities of the integration
|
* @param {string} [activities] new list of activities of the integration
|
||||||
* @return_type {_id: string}
|
* @return_type {_id: string}
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('PUT', '/api/boards/:boardId/integrations/:intId', function (req, res) {
|
JsonRoutes.add('PUT', '/api/boards/:boardId/integrations/:intId', function(
|
||||||
|
req,
|
||||||
|
res
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const paramBoardId = req.params.boardId;
|
const paramBoardId = req.params.boardId;
|
||||||
const paramIntId = req.params.intId;
|
const paramIntId = req.params.intId;
|
||||||
|
|
@ -200,28 +237,38 @@ if (Meteor.isServer) {
|
||||||
|
|
||||||
if (req.body.hasOwnProperty('enabled')) {
|
if (req.body.hasOwnProperty('enabled')) {
|
||||||
const newEnabled = req.body.enabled;
|
const newEnabled = req.body.enabled;
|
||||||
Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
|
Integrations.direct.update(
|
||||||
{$set: {enabled: newEnabled}});
|
{ _id: paramIntId, boardId: paramBoardId },
|
||||||
|
{ $set: { enabled: newEnabled } }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (req.body.hasOwnProperty('title')) {
|
if (req.body.hasOwnProperty('title')) {
|
||||||
const newTitle = req.body.title;
|
const newTitle = req.body.title;
|
||||||
Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
|
Integrations.direct.update(
|
||||||
{$set: {title: newTitle}});
|
{ _id: paramIntId, boardId: paramBoardId },
|
||||||
|
{ $set: { title: newTitle } }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (req.body.hasOwnProperty('url')) {
|
if (req.body.hasOwnProperty('url')) {
|
||||||
const newUrl = req.body.url;
|
const newUrl = req.body.url;
|
||||||
Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
|
Integrations.direct.update(
|
||||||
{$set: {url: newUrl}});
|
{ _id: paramIntId, boardId: paramBoardId },
|
||||||
|
{ $set: { url: newUrl } }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (req.body.hasOwnProperty('token')) {
|
if (req.body.hasOwnProperty('token')) {
|
||||||
const newToken = req.body.token;
|
const newToken = req.body.token;
|
||||||
Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
|
Integrations.direct.update(
|
||||||
{$set: {token: newToken}});
|
{ _id: paramIntId, boardId: paramBoardId },
|
||||||
|
{ $set: { token: newToken } }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (req.body.hasOwnProperty('activities')) {
|
if (req.body.hasOwnProperty('activities')) {
|
||||||
const newActivities = req.body.activities;
|
const newActivities = req.body.activities;
|
||||||
Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
|
Integrations.direct.update(
|
||||||
{$set: {activities: newActivities}});
|
{ _id: paramIntId, boardId: paramBoardId },
|
||||||
|
{ $set: { activities: newActivities } }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
|
|
@ -230,8 +277,7 @@ if (Meteor.isServer) {
|
||||||
_id: paramIntId,
|
_id: paramIntId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: error,
|
data: error,
|
||||||
|
|
@ -248,28 +294,36 @@ if (Meteor.isServer) {
|
||||||
* @param {string} newActivities the activities to remove from the integration
|
* @param {string} newActivities the activities to remove from the integration
|
||||||
* @return_type Integrations
|
* @return_type Integrations
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('DELETE', '/api/boards/:boardId/integrations/:intId/activities', function (req, res) {
|
JsonRoutes.add(
|
||||||
try {
|
'DELETE',
|
||||||
const paramBoardId = req.params.boardId;
|
'/api/boards/:boardId/integrations/:intId/activities',
|
||||||
const paramIntId = req.params.intId;
|
function(req, res) {
|
||||||
const newActivities = req.body.activities;
|
try {
|
||||||
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
const paramBoardId = req.params.boardId;
|
||||||
|
const paramIntId = req.params.intId;
|
||||||
|
const newActivities = req.body.activities;
|
||||||
|
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
||||||
|
|
||||||
Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
|
Integrations.direct.update(
|
||||||
{$pullAll: {activities: newActivities}});
|
{ _id: paramIntId, boardId: paramBoardId },
|
||||||
|
{ $pullAll: { activities: newActivities } }
|
||||||
|
);
|
||||||
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: Integrations.findOne({_id: paramIntId, boardId: paramBoardId}, { fields: {_id: 1, activities: 1}}),
|
data: Integrations.findOne(
|
||||||
});
|
{ _id: paramIntId, boardId: paramBoardId },
|
||||||
|
{ fields: { _id: 1, activities: 1 } }
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
JsonRoutes.sendResult(res, {
|
||||||
|
code: 200,
|
||||||
|
data: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
);
|
||||||
JsonRoutes.sendResult(res, {
|
|
||||||
code: 200,
|
|
||||||
data: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @operation new_integration_activities
|
* @operation new_integration_activities
|
||||||
|
|
@ -280,28 +334,36 @@ if (Meteor.isServer) {
|
||||||
* @param {string} newActivities the activities to add to the integration
|
* @param {string} newActivities the activities to add to the integration
|
||||||
* @return_type Integrations
|
* @return_type Integrations
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('POST', '/api/boards/:boardId/integrations/:intId/activities', function (req, res) {
|
JsonRoutes.add(
|
||||||
try {
|
'POST',
|
||||||
const paramBoardId = req.params.boardId;
|
'/api/boards/:boardId/integrations/:intId/activities',
|
||||||
const paramIntId = req.params.intId;
|
function(req, res) {
|
||||||
const newActivities = req.body.activities;
|
try {
|
||||||
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
const paramBoardId = req.params.boardId;
|
||||||
|
const paramIntId = req.params.intId;
|
||||||
|
const newActivities = req.body.activities;
|
||||||
|
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
||||||
|
|
||||||
Integrations.direct.update({_id: paramIntId, boardId: paramBoardId},
|
Integrations.direct.update(
|
||||||
{$addToSet: {activities: { $each: newActivities}}});
|
{ _id: paramIntId, boardId: paramBoardId },
|
||||||
|
{ $addToSet: { activities: { $each: newActivities } } }
|
||||||
|
);
|
||||||
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: Integrations.findOne({_id: paramIntId, boardId: paramBoardId}, { fields: {_id: 1, activities: 1}}),
|
data: Integrations.findOne(
|
||||||
});
|
{ _id: paramIntId, boardId: paramBoardId },
|
||||||
|
{ fields: { _id: 1, activities: 1 } }
|
||||||
|
),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
JsonRoutes.sendResult(res, {
|
||||||
|
code: 200,
|
||||||
|
data: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
);
|
||||||
JsonRoutes.sendResult(res, {
|
|
||||||
code: 200,
|
|
||||||
data: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @operation delete_integration
|
* @operation delete_integration
|
||||||
|
|
@ -311,21 +373,23 @@ if (Meteor.isServer) {
|
||||||
* @param {string} intId the integration ID
|
* @param {string} intId the integration ID
|
||||||
* @return_type {_id: string}
|
* @return_type {_id: string}
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('DELETE', '/api/boards/:boardId/integrations/:intId', function (req, res) {
|
JsonRoutes.add('DELETE', '/api/boards/:boardId/integrations/:intId', function(
|
||||||
|
req,
|
||||||
|
res
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const paramBoardId = req.params.boardId;
|
const paramBoardId = req.params.boardId;
|
||||||
const paramIntId = req.params.intId;
|
const paramIntId = req.params.intId;
|
||||||
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
||||||
|
|
||||||
Integrations.direct.remove({_id: paramIntId, boardId: paramBoardId});
|
Integrations.direct.remove({ _id: paramIntId, boardId: paramBoardId });
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: {
|
data: {
|
||||||
_id: paramIntId,
|
_id: paramIntId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: error,
|
data: error,
|
||||||
|
|
@ -333,3 +397,5 @@ if (Meteor.isServer) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Integrations;
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,78 @@
|
||||||
InvitationCodes = new Mongo.Collection('invitation_codes');
|
InvitationCodes = new Mongo.Collection('invitation_codes');
|
||||||
|
|
||||||
InvitationCodes.attachSchema(new SimpleSchema({
|
InvitationCodes.attachSchema(
|
||||||
code: {
|
new SimpleSchema({
|
||||||
type: String,
|
code: {
|
||||||
},
|
type: String,
|
||||||
email: {
|
},
|
||||||
type: String,
|
email: {
|
||||||
unique: true,
|
type: String,
|
||||||
regEx: SimpleSchema.RegEx.Email,
|
unique: true,
|
||||||
},
|
regEx: SimpleSchema.RegEx.Email,
|
||||||
createdAt: {
|
},
|
||||||
type: Date,
|
createdAt: {
|
||||||
denyUpdate: false,
|
type: Date,
|
||||||
},
|
denyUpdate: false,
|
||||||
// always be the admin if only one admin
|
optional: true,
|
||||||
authorId: {
|
// eslint-disable-next-line consistent-return
|
||||||
type: String,
|
autoValue() {
|
||||||
},
|
if (this.isInsert) {
|
||||||
boardsToBeInvited: {
|
return new Date();
|
||||||
type: [String],
|
} else {
|
||||||
optional: true,
|
this.unset();
|
||||||
},
|
}
|
||||||
valid: {
|
},
|
||||||
type: Boolean,
|
},
|
||||||
defaultValue: true,
|
modifiedAt: {
|
||||||
},
|
type: Date,
|
||||||
}));
|
denyUpdate: false,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert || this.isUpsert || this.isUpdate) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// always be the admin if only one admin
|
||||||
|
authorId: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
boardsToBeInvited: {
|
||||||
|
type: [String],
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
valid: {
|
||||||
|
type: Boolean,
|
||||||
|
defaultValue: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
InvitationCodes.helpers({
|
InvitationCodes.helpers({
|
||||||
author(){
|
author() {
|
||||||
return Users.findOne(this.authorId);
|
return Users.findOne(this.authorId);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
InvitationCodes.before.update((userId, doc, fieldNames, modifier, options) => {
|
||||||
|
modifier.$set = modifier.$set || {};
|
||||||
|
modifier.$set.modifiedAt = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
// InvitationCodes.before.insert((userId, doc) => {
|
// InvitationCodes.before.insert((userId, doc) => {
|
||||||
// doc.createdAt = new Date();
|
// doc.createdAt = new Date();
|
||||||
// doc.authorId = userId;
|
// doc.authorId = userId;
|
||||||
// });
|
// });
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
|
Meteor.startup(() => {
|
||||||
|
InvitationCodes._collection._ensureIndex({ modifiedAt: -1 });
|
||||||
|
});
|
||||||
Boards.deny({
|
Boards.deny({
|
||||||
fetch: ['members'],
|
fetch: ['members'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default InvitationCodes;
|
||||||
|
|
|
||||||
361
models/lists.js
361
models/lists.js
|
|
@ -3,125 +3,161 @@ Lists = new Mongo.Collection('lists');
|
||||||
/**
|
/**
|
||||||
* A list (column) in the Wekan board.
|
* A list (column) in the Wekan board.
|
||||||
*/
|
*/
|
||||||
Lists.attachSchema(new SimpleSchema({
|
Lists.attachSchema(
|
||||||
title: {
|
new SimpleSchema({
|
||||||
/**
|
title: {
|
||||||
* the title of the list
|
/**
|
||||||
*/
|
* the title of the list
|
||||||
type: String,
|
*/
|
||||||
},
|
type: String,
|
||||||
archived: {
|
|
||||||
/**
|
|
||||||
* is the list archived
|
|
||||||
*/
|
|
||||||
type: Boolean,
|
|
||||||
autoValue() { // eslint-disable-line consistent-return
|
|
||||||
if (this.isInsert && !this.isSet) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
archived: {
|
||||||
boardId: {
|
/**
|
||||||
/**
|
* is the list archived
|
||||||
* the board associated to this list
|
*/
|
||||||
*/
|
type: Boolean,
|
||||||
type: String,
|
// eslint-disable-next-line consistent-return
|
||||||
},
|
autoValue() {
|
||||||
swimlaneId: {
|
if (this.isInsert && !this.isSet) {
|
||||||
/**
|
return false;
|
||||||
* the swimlane associated to this list. Used for templates
|
}
|
||||||
*/
|
},
|
||||||
type: String,
|
|
||||||
defaultValue: '',
|
|
||||||
},
|
|
||||||
createdAt: {
|
|
||||||
/**
|
|
||||||
* creation date
|
|
||||||
*/
|
|
||||||
type: Date,
|
|
||||||
autoValue() { // eslint-disable-line consistent-return
|
|
||||||
if (this.isInsert) {
|
|
||||||
return new Date();
|
|
||||||
} else {
|
|
||||||
this.unset();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
boardId: {
|
||||||
sort: {
|
/**
|
||||||
/**
|
* the board associated to this list
|
||||||
* is the list sorted
|
*/
|
||||||
*/
|
type: String,
|
||||||
type: Number,
|
|
||||||
decimal: true,
|
|
||||||
// XXX We should probably provide a default
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
updatedAt: {
|
|
||||||
/**
|
|
||||||
* last update of the list
|
|
||||||
*/
|
|
||||||
type: Date,
|
|
||||||
optional: true,
|
|
||||||
autoValue() { // eslint-disable-line consistent-return
|
|
||||||
if (this.isUpdate) {
|
|
||||||
return new Date();
|
|
||||||
} else {
|
|
||||||
this.unset();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
swimlaneId: {
|
||||||
wipLimit: {
|
/**
|
||||||
/**
|
* the swimlane associated to this list. Used for templates
|
||||||
* WIP object, see below
|
*/
|
||||||
*/
|
type: String,
|
||||||
type: Object,
|
defaultValue: '',
|
||||||
optional: true,
|
},
|
||||||
},
|
createdAt: {
|
||||||
'wipLimit.value': {
|
/**
|
||||||
/**
|
* creation date
|
||||||
* value of the WIP
|
*/
|
||||||
*/
|
type: Date,
|
||||||
type: Number,
|
// eslint-disable-next-line consistent-return
|
||||||
decimal: false,
|
autoValue() {
|
||||||
defaultValue: 1,
|
if (this.isInsert) {
|
||||||
},
|
return new Date();
|
||||||
'wipLimit.enabled': {
|
} else {
|
||||||
/**
|
this.unset();
|
||||||
* is the WIP enabled
|
}
|
||||||
*/
|
},
|
||||||
type: Boolean,
|
},
|
||||||
defaultValue: false,
|
sort: {
|
||||||
},
|
/**
|
||||||
'wipLimit.soft': {
|
* is the list sorted
|
||||||
/**
|
*/
|
||||||
* is the WIP a soft or hard requirement
|
type: Number,
|
||||||
*/
|
decimal: true,
|
||||||
type: Boolean,
|
// XXX We should probably provide a default
|
||||||
defaultValue: false,
|
optional: true,
|
||||||
},
|
},
|
||||||
color: {
|
updatedAt: {
|
||||||
/**
|
/**
|
||||||
* the color of the list
|
* last update of the list
|
||||||
*/
|
*/
|
||||||
type: String,
|
type: Date,
|
||||||
optional: true,
|
optional: true,
|
||||||
// silver is the default, so it is left out
|
// eslint-disable-next-line consistent-return
|
||||||
allowedValues: [
|
autoValue() {
|
||||||
'white', 'green', 'yellow', 'orange', 'red', 'purple',
|
if (this.isUpdate || this.isUpsert || this.isInsert) {
|
||||||
'blue', 'sky', 'lime', 'pink', 'black',
|
return new Date();
|
||||||
'peachpuff', 'crimson', 'plum', 'darkgreen',
|
} else {
|
||||||
'slateblue', 'magenta', 'gold', 'navy', 'gray',
|
this.unset();
|
||||||
'saddlebrown', 'paleturquoise', 'mistyrose', 'indigo',
|
}
|
||||||
],
|
},
|
||||||
},
|
},
|
||||||
type: {
|
modifiedAt: {
|
||||||
/**
|
type: Date,
|
||||||
* The type of list
|
denyUpdate: false,
|
||||||
*/
|
// eslint-disable-next-line consistent-return
|
||||||
type: String,
|
autoValue() {
|
||||||
defaultValue: 'list',
|
if (this.isInsert || this.isUpsert || this.isUpdate) {
|
||||||
},
|
return new Date();
|
||||||
}));
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wipLimit: {
|
||||||
|
/**
|
||||||
|
* WIP object, see below
|
||||||
|
*/
|
||||||
|
type: Object,
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
'wipLimit.value': {
|
||||||
|
/**
|
||||||
|
* value of the WIP
|
||||||
|
*/
|
||||||
|
type: Number,
|
||||||
|
decimal: false,
|
||||||
|
defaultValue: 1,
|
||||||
|
},
|
||||||
|
'wipLimit.enabled': {
|
||||||
|
/**
|
||||||
|
* is the WIP enabled
|
||||||
|
*/
|
||||||
|
type: Boolean,
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
'wipLimit.soft': {
|
||||||
|
/**
|
||||||
|
* is the WIP a soft or hard requirement
|
||||||
|
*/
|
||||||
|
type: Boolean,
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
/**
|
||||||
|
* the color of the list
|
||||||
|
*/
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
// silver is the default, so it is left out
|
||||||
|
allowedValues: [
|
||||||
|
'white',
|
||||||
|
'green',
|
||||||
|
'yellow',
|
||||||
|
'orange',
|
||||||
|
'red',
|
||||||
|
'purple',
|
||||||
|
'blue',
|
||||||
|
'sky',
|
||||||
|
'lime',
|
||||||
|
'pink',
|
||||||
|
'black',
|
||||||
|
'peachpuff',
|
||||||
|
'crimson',
|
||||||
|
'plum',
|
||||||
|
'darkgreen',
|
||||||
|
'slateblue',
|
||||||
|
'magenta',
|
||||||
|
'gold',
|
||||||
|
'navy',
|
||||||
|
'gray',
|
||||||
|
'saddlebrown',
|
||||||
|
'paleturquoise',
|
||||||
|
'mistyrose',
|
||||||
|
'indigo',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
/**
|
||||||
|
* The type of list
|
||||||
|
*/
|
||||||
|
type: String,
|
||||||
|
defaultValue: 'list',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
Lists.allow({
|
Lists.allow({
|
||||||
insert(userId, doc) {
|
insert(userId, doc) {
|
||||||
|
|
@ -172,10 +208,8 @@ Lists.helpers({
|
||||||
listId: this._id,
|
listId: this._id,
|
||||||
archived: false,
|
archived: false,
|
||||||
};
|
};
|
||||||
if (swimlaneId)
|
if (swimlaneId) selector.swimlaneId = swimlaneId;
|
||||||
selector.swimlaneId = swimlaneId;
|
return Cards.find(Filter.mongoSelector(selector), { sort: ['sort'] });
|
||||||
return Cards.find(Filter.mongoSelector(selector),
|
|
||||||
{ sort: ['sort'] });
|
|
||||||
},
|
},
|
||||||
|
|
||||||
cardsUnfiltered(swimlaneId) {
|
cardsUnfiltered(swimlaneId) {
|
||||||
|
|
@ -183,10 +217,8 @@ Lists.helpers({
|
||||||
listId: this._id,
|
listId: this._id,
|
||||||
archived: false,
|
archived: false,
|
||||||
};
|
};
|
||||||
if (swimlaneId)
|
if (swimlaneId) selector.swimlaneId = swimlaneId;
|
||||||
selector.swimlaneId = swimlaneId;
|
return Cards.find(selector, { sort: ['sort'] });
|
||||||
return Cards.find(selector,
|
|
||||||
{ sort: ['sort'] });
|
|
||||||
},
|
},
|
||||||
|
|
||||||
allCards() {
|
allCards() {
|
||||||
|
|
@ -197,11 +229,12 @@ Lists.helpers({
|
||||||
return Boards.findOne(this.boardId);
|
return Boards.findOne(this.boardId);
|
||||||
},
|
},
|
||||||
|
|
||||||
getWipLimit(option){
|
getWipLimit(option) {
|
||||||
const list = Lists.findOne({ _id: this._id });
|
const list = Lists.findOne({ _id: this._id });
|
||||||
if(!list.wipLimit) { // Necessary check to avoid exceptions for the case where the doc doesn't have the wipLimit field yet set
|
if (!list.wipLimit) {
|
||||||
|
// Necessary check to avoid exceptions for the case where the doc doesn't have the wipLimit field yet set
|
||||||
return 0;
|
return 0;
|
||||||
} else if(!option) {
|
} else if (!option) {
|
||||||
return list.wipLimit;
|
return list.wipLimit;
|
||||||
} else {
|
} else {
|
||||||
return list.wipLimit[option] ? list.wipLimit[option] : 0; // Necessary check to avoid exceptions for the case where the doc doesn't have the wipLimit field yet set
|
return list.wipLimit[option] ? list.wipLimit[option] : 0; // Necessary check to avoid exceptions for the case where the doc doesn't have the wipLimit field yet set
|
||||||
|
|
@ -209,8 +242,7 @@ Lists.helpers({
|
||||||
},
|
},
|
||||||
|
|
||||||
colorClass() {
|
colorClass() {
|
||||||
if (this.color)
|
if (this.color) return this.color;
|
||||||
return this.color;
|
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -219,7 +251,7 @@ Lists.helpers({
|
||||||
},
|
},
|
||||||
|
|
||||||
remove() {
|
remove() {
|
||||||
Lists.remove({ _id: this._id});
|
Lists.remove({ _id: this._id });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -271,10 +303,10 @@ Lists.mutations({
|
||||||
});
|
});
|
||||||
|
|
||||||
Meteor.methods({
|
Meteor.methods({
|
||||||
applyWipLimit(listId, limit){
|
applyWipLimit(listId, limit) {
|
||||||
check(listId, String);
|
check(listId, String);
|
||||||
check(limit, Number);
|
check(limit, Number);
|
||||||
if(limit === 0){
|
if (limit === 0) {
|
||||||
limit = 1;
|
limit = 1;
|
||||||
}
|
}
|
||||||
Lists.findOne({ _id: listId }).setWipLimit(limit);
|
Lists.findOne({ _id: listId }).setWipLimit(limit);
|
||||||
|
|
@ -283,7 +315,7 @@ Meteor.methods({
|
||||||
enableWipLimit(listId) {
|
enableWipLimit(listId) {
|
||||||
check(listId, String);
|
check(listId, String);
|
||||||
const list = Lists.findOne({ _id: listId });
|
const list = Lists.findOne({ _id: listId });
|
||||||
if(list.getWipLimit('value') === 0){
|
if (list.getWipLimit('value') === 0) {
|
||||||
list.setWipLimit(1);
|
list.setWipLimit(1);
|
||||||
}
|
}
|
||||||
list.toggleWipLimit(!list.getWipLimit('enabled'));
|
list.toggleWipLimit(!list.getWipLimit('enabled'));
|
||||||
|
|
@ -300,6 +332,7 @@ Lists.hookOptions.after.update = { fetchPrevious: false };
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
|
Lists._collection._ensureIndex({ modifiedAt: -1 });
|
||||||
Lists._collection._ensureIndex({ boardId: 1 });
|
Lists._collection._ensureIndex({ boardId: 1 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -313,6 +346,11 @@ if (Meteor.isServer) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Lists.before.update((userId, doc, fieldNames, modifier, options) => {
|
||||||
|
modifier.$set = modifier.$set || {};
|
||||||
|
modifier.$set.modifiedAt = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
Lists.before.remove((userId, doc) => {
|
Lists.before.remove((userId, doc) => {
|
||||||
const cards = Cards.find({ listId: doc._id });
|
const cards = Cards.find({ listId: doc._id });
|
||||||
if (cards) {
|
if (cards) {
|
||||||
|
|
@ -353,22 +391,23 @@ if (Meteor.isServer) {
|
||||||
* @return_type [{_id: string,
|
* @return_type [{_id: string,
|
||||||
* title: string}]
|
* title: string}]
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('GET', '/api/boards/:boardId/lists', function (req, res) {
|
JsonRoutes.add('GET', '/api/boards/:boardId/lists', function(req, res) {
|
||||||
try {
|
try {
|
||||||
const paramBoardId = req.params.boardId;
|
const paramBoardId = req.params.boardId;
|
||||||
Authentication.checkBoardAccess( req.userId, paramBoardId);
|
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
||||||
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: Lists.find({ boardId: paramBoardId, archived: false }).map(function (doc) {
|
data: Lists.find({ boardId: paramBoardId, archived: false }).map(
|
||||||
return {
|
function(doc) {
|
||||||
_id: doc._id,
|
return {
|
||||||
title: doc.title,
|
_id: doc._id,
|
||||||
};
|
title: doc.title,
|
||||||
}),
|
};
|
||||||
|
}
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: error,
|
data: error,
|
||||||
|
|
@ -384,17 +423,23 @@ if (Meteor.isServer) {
|
||||||
* @param {string} listId the List ID
|
* @param {string} listId the List ID
|
||||||
* @return_type Lists
|
* @return_type Lists
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId', function (req, res) {
|
JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId', function(
|
||||||
|
req,
|
||||||
|
res
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const paramBoardId = req.params.boardId;
|
const paramBoardId = req.params.boardId;
|
||||||
const paramListId = req.params.listId;
|
const paramListId = req.params.listId;
|
||||||
Authentication.checkBoardAccess( req.userId, paramBoardId);
|
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: Lists.findOne({ _id: paramListId, boardId: paramBoardId, archived: false }),
|
data: Lists.findOne({
|
||||||
|
_id: paramListId,
|
||||||
|
boardId: paramBoardId,
|
||||||
|
archived: false,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: error,
|
data: error,
|
||||||
|
|
@ -410,9 +455,9 @@ if (Meteor.isServer) {
|
||||||
* @param {string} title the title of the List
|
* @param {string} title the title of the List
|
||||||
* @return_type {_id: string}
|
* @return_type {_id: string}
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('POST', '/api/boards/:boardId/lists', function (req, res) {
|
JsonRoutes.add('POST', '/api/boards/:boardId/lists', function(req, res) {
|
||||||
try {
|
try {
|
||||||
Authentication.checkUserId( req.userId);
|
Authentication.checkUserId(req.userId);
|
||||||
const paramBoardId = req.params.boardId;
|
const paramBoardId = req.params.boardId;
|
||||||
const board = Boards.findOne(paramBoardId);
|
const board = Boards.findOne(paramBoardId);
|
||||||
const id = Lists.insert({
|
const id = Lists.insert({
|
||||||
|
|
@ -426,8 +471,7 @@ if (Meteor.isServer) {
|
||||||
_id: id,
|
_id: id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: error,
|
data: error,
|
||||||
|
|
@ -446,9 +490,12 @@ if (Meteor.isServer) {
|
||||||
* @param {string} listId the ID of the list to remove
|
* @param {string} listId the ID of the list to remove
|
||||||
* @return_type {_id: string}
|
* @return_type {_id: string}
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId', function (req, res) {
|
JsonRoutes.add('DELETE', '/api/boards/:boardId/lists/:listId', function(
|
||||||
|
req,
|
||||||
|
res
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
Authentication.checkUserId( req.userId);
|
Authentication.checkUserId(req.userId);
|
||||||
const paramBoardId = req.params.boardId;
|
const paramBoardId = req.params.boardId;
|
||||||
const paramListId = req.params.listId;
|
const paramListId = req.params.listId;
|
||||||
Lists.remove({ _id: paramListId, boardId: paramBoardId });
|
Lists.remove({ _id: paramListId, boardId: paramBoardId });
|
||||||
|
|
@ -458,13 +505,13 @@ if (Meteor.isServer) {
|
||||||
_id: paramListId,
|
_id: paramListId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: error,
|
data: error,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Lists;
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,51 @@
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
|
||||||
Rules = new Mongo.Collection('rules');
|
Rules = new Mongo.Collection('rules');
|
||||||
|
|
||||||
Rules.attachSchema(new SimpleSchema({
|
Rules.attachSchema(
|
||||||
title: {
|
new SimpleSchema({
|
||||||
type: String,
|
title: {
|
||||||
optional: false,
|
type: String,
|
||||||
},
|
optional: false,
|
||||||
triggerId: {
|
},
|
||||||
type: String,
|
triggerId: {
|
||||||
optional: false,
|
type: String,
|
||||||
},
|
optional: false,
|
||||||
actionId: {
|
},
|
||||||
type: String,
|
actionId: {
|
||||||
optional: false,
|
type: String,
|
||||||
},
|
optional: false,
|
||||||
boardId: {
|
},
|
||||||
type: String,
|
boardId: {
|
||||||
optional: false,
|
type: String,
|
||||||
},
|
optional: false,
|
||||||
}));
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: Date,
|
||||||
|
optional: true,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modifiedAt: {
|
||||||
|
type: Date,
|
||||||
|
denyUpdate: false,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert || this.isUpsert || this.isUpdate) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
Rules.mutations({
|
Rules.mutations({
|
||||||
rename(description) {
|
rename(description) {
|
||||||
|
|
@ -26,15 +54,14 @@ Rules.mutations({
|
||||||
});
|
});
|
||||||
|
|
||||||
Rules.helpers({
|
Rules.helpers({
|
||||||
getAction(){
|
getAction() {
|
||||||
return Actions.findOne({_id:this.actionId});
|
return Actions.findOne({ _id: this.actionId });
|
||||||
},
|
},
|
||||||
getTrigger(){
|
getTrigger() {
|
||||||
return Triggers.findOne({_id:this.triggerId});
|
return Triggers.findOne({ _id: this.triggerId });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
Rules.allow({
|
Rules.allow({
|
||||||
insert(userId, doc) {
|
insert(userId, doc) {
|
||||||
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
|
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
|
||||||
|
|
@ -46,3 +73,16 @@ Rules.allow({
|
||||||
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
|
return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Rules.before.update((userId, doc, fieldNames, modifier, options) => {
|
||||||
|
modifier.$set = modifier.$set || {};
|
||||||
|
modifier.$set.modifiedAt = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Meteor.isServer) {
|
||||||
|
Meteor.startup(() => {
|
||||||
|
Rules._collection._ensureIndex({ modifiedAt: -1 });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Rules;
|
||||||
|
|
|
||||||
|
|
@ -1,67 +1,85 @@
|
||||||
Settings = new Mongo.Collection('settings');
|
Settings = new Mongo.Collection('settings');
|
||||||
|
|
||||||
Settings.attachSchema(new SimpleSchema({
|
Settings.attachSchema(
|
||||||
disableRegistration: {
|
new SimpleSchema({
|
||||||
type: Boolean,
|
disableRegistration: {
|
||||||
},
|
type: Boolean,
|
||||||
'mailServer.username': {
|
},
|
||||||
type: String,
|
'mailServer.username': {
|
||||||
optional: true,
|
type: String,
|
||||||
},
|
optional: true,
|
||||||
'mailServer.password': {
|
},
|
||||||
type: String,
|
'mailServer.password': {
|
||||||
optional: true,
|
type: String,
|
||||||
},
|
optional: true,
|
||||||
'mailServer.host': {
|
},
|
||||||
type: String,
|
'mailServer.host': {
|
||||||
optional: true,
|
type: String,
|
||||||
},
|
optional: true,
|
||||||
'mailServer.port': {
|
},
|
||||||
type: String,
|
'mailServer.port': {
|
||||||
optional: true,
|
type: String,
|
||||||
},
|
optional: true,
|
||||||
'mailServer.enableTLS': {
|
},
|
||||||
type: Boolean,
|
'mailServer.enableTLS': {
|
||||||
optional: true,
|
type: Boolean,
|
||||||
},
|
optional: true,
|
||||||
'mailServer.from': {
|
},
|
||||||
type: String,
|
'mailServer.from': {
|
||||||
optional: true,
|
type: String,
|
||||||
},
|
optional: true,
|
||||||
productName: {
|
},
|
||||||
type: String,
|
productName: {
|
||||||
optional: true,
|
type: String,
|
||||||
},
|
optional: true,
|
||||||
customHTMLafterBodyStart: {
|
},
|
||||||
type: String,
|
customHTMLafterBodyStart: {
|
||||||
optional: true,
|
type: String,
|
||||||
},
|
optional: true,
|
||||||
customHTMLbeforeBodyEnd: {
|
},
|
||||||
type: String,
|
customHTMLbeforeBodyEnd: {
|
||||||
optional: true,
|
type: String,
|
||||||
},
|
optional: true,
|
||||||
displayAuthenticationMethod: {
|
},
|
||||||
type: Boolean,
|
displayAuthenticationMethod: {
|
||||||
optional: true,
|
type: Boolean,
|
||||||
},
|
optional: true,
|
||||||
defaultAuthenticationMethod: {
|
},
|
||||||
type: String,
|
defaultAuthenticationMethod: {
|
||||||
optional: false,
|
type: String,
|
||||||
},
|
optional: false,
|
||||||
hideLogo: {
|
},
|
||||||
type: Boolean,
|
hideLogo: {
|
||||||
optional: true,
|
type: Boolean,
|
||||||
},
|
optional: true,
|
||||||
createdAt: {
|
},
|
||||||
type: Date,
|
createdAt: {
|
||||||
denyUpdate: true,
|
type: Date,
|
||||||
},
|
denyUpdate: true,
|
||||||
modifiedAt: {
|
// eslint-disable-next-line consistent-return
|
||||||
type: Date,
|
autoValue() {
|
||||||
},
|
if (this.isInsert) {
|
||||||
}));
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modifiedAt: {
|
||||||
|
type: Date,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert || this.isUpsert || this.isUpdate) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
Settings.helpers({
|
Settings.helpers({
|
||||||
mailUrl () {
|
mailUrl() {
|
||||||
if (!this.mailServer.host) {
|
if (!this.mailServer.host) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -69,7 +87,9 @@ Settings.helpers({
|
||||||
if (!this.mailServer.username && !this.mailServer.password) {
|
if (!this.mailServer.username && !this.mailServer.password) {
|
||||||
return `${protocol}${this.mailServer.host}:${this.mailServer.port}/`;
|
return `${protocol}${this.mailServer.host}:${this.mailServer.port}/`;
|
||||||
}
|
}
|
||||||
return `${protocol}${this.mailServer.username}:${encodeURIComponent(this.mailServer.password)}@${this.mailServer.host}:${this.mailServer.port}/`;
|
return `${protocol}${this.mailServer.username}:${encodeURIComponent(
|
||||||
|
this.mailServer.password
|
||||||
|
)}@${this.mailServer.host}:${this.mailServer.port}/`;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
Settings.allow({
|
Settings.allow({
|
||||||
|
|
@ -86,50 +106,75 @@ Settings.before.update((userId, doc, fieldNames, modifier) => {
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
|
Settings._collection._ensureIndex({ modifiedAt: -1 });
|
||||||
const setting = Settings.findOne({});
|
const setting = Settings.findOne({});
|
||||||
if(!setting){
|
if (!setting) {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const domain = process.env.ROOT_URL.match(/\/\/(?:www\.)?(.*)?(?:\/)?/)[1];
|
const domain = process.env.ROOT_URL.match(
|
||||||
|
/\/\/(?:www\.)?(.*)?(?:\/)?/
|
||||||
|
)[1];
|
||||||
const from = `Boards Support <support@${domain}>`;
|
const from = `Boards Support <support@${domain}>`;
|
||||||
const defaultSetting = {disableRegistration: false, mailServer: {
|
const defaultSetting = {
|
||||||
username: '', password: '', host: '', port: '', enableTLS: false, from,
|
disableRegistration: false,
|
||||||
}, createdAt: now, modifiedAt: now, displayAuthenticationMethod: true,
|
mailServer: {
|
||||||
defaultAuthenticationMethod: 'password'};
|
username: '',
|
||||||
|
password: '',
|
||||||
|
host: '',
|
||||||
|
port: '',
|
||||||
|
enableTLS: false,
|
||||||
|
from,
|
||||||
|
},
|
||||||
|
createdAt: now,
|
||||||
|
modifiedAt: now,
|
||||||
|
displayAuthenticationMethod: true,
|
||||||
|
defaultAuthenticationMethod: 'password',
|
||||||
|
};
|
||||||
Settings.insert(defaultSetting);
|
Settings.insert(defaultSetting);
|
||||||
}
|
}
|
||||||
const newSetting = Settings.findOne();
|
const newSetting = Settings.findOne();
|
||||||
if (!process.env.MAIL_URL && newSetting.mailUrl())
|
if (!process.env.MAIL_URL && newSetting.mailUrl())
|
||||||
process.env.MAIL_URL = newSetting.mailUrl();
|
process.env.MAIL_URL = newSetting.mailUrl();
|
||||||
Accounts.emailTemplates.from = process.env.MAIL_FROM ? process.env.MAIL_FROM : newSetting.mailServer.from;
|
Accounts.emailTemplates.from = process.env.MAIL_FROM
|
||||||
|
? process.env.MAIL_FROM
|
||||||
|
: newSetting.mailServer.from;
|
||||||
});
|
});
|
||||||
Settings.after.update((userId, doc, fieldNames) => {
|
Settings.after.update((userId, doc, fieldNames) => {
|
||||||
// assign new values to mail-from & MAIL_URL in environment
|
// assign new values to mail-from & MAIL_URL in environment
|
||||||
if (_.contains(fieldNames, 'mailServer') && doc.mailServer.host) {
|
if (_.contains(fieldNames, 'mailServer') && doc.mailServer.host) {
|
||||||
const protocol = doc.mailServer.enableTLS ? 'smtps://' : 'smtp://';
|
const protocol = doc.mailServer.enableTLS ? 'smtps://' : 'smtp://';
|
||||||
if (!doc.mailServer.username && !doc.mailServer.password) {
|
if (!doc.mailServer.username && !doc.mailServer.password) {
|
||||||
process.env.MAIL_URL = `${protocol}${doc.mailServer.host}:${doc.mailServer.port}/`;
|
process.env.MAIL_URL = `${protocol}${doc.mailServer.host}:${
|
||||||
|
doc.mailServer.port
|
||||||
|
}/`;
|
||||||
} else {
|
} else {
|
||||||
process.env.MAIL_URL = `${protocol}${doc.mailServer.username}:${encodeURIComponent(doc.mailServer.password)}@${doc.mailServer.host}:${doc.mailServer.port}/`;
|
process.env.MAIL_URL = `${protocol}${
|
||||||
|
doc.mailServer.username
|
||||||
|
}:${encodeURIComponent(doc.mailServer.password)}@${
|
||||||
|
doc.mailServer.host
|
||||||
|
}:${doc.mailServer.port}/`;
|
||||||
}
|
}
|
||||||
Accounts.emailTemplates.from = doc.mailServer.from;
|
Accounts.emailTemplates.from = doc.mailServer.from;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function getRandomNum (min, max) {
|
function getRandomNum(min, max) {
|
||||||
const range = max - min;
|
const range = max - min;
|
||||||
const rand = Math.random();
|
const rand = Math.random();
|
||||||
return (min + Math.round(rand * range));
|
return min + Math.round(rand * range);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEnvVar(name){
|
function getEnvVar(name) {
|
||||||
const value = process.env[name];
|
const value = process.env[name];
|
||||||
if (value){
|
if (value) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
throw new Meteor.Error(['var-not-exist', `The environment variable ${name} does not exist`]);
|
throw new Meteor.Error([
|
||||||
|
'var-not-exist',
|
||||||
|
`The environment variable ${name} does not exist`,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendInvitationEmail (_id){
|
function sendInvitationEmail(_id) {
|
||||||
const icode = InvitationCodes.findOne(_id);
|
const icode = InvitationCodes.findOne(_id);
|
||||||
const author = Users.findOne(Meteor.userId());
|
const author = Users.findOne(Meteor.userId());
|
||||||
try {
|
try {
|
||||||
|
|
@ -172,30 +217,47 @@ if (Meteor.isServer) {
|
||||||
check(boards, [String]);
|
check(boards, [String]);
|
||||||
|
|
||||||
const user = Users.findOne(Meteor.userId());
|
const user = Users.findOne(Meteor.userId());
|
||||||
if(!user.isAdmin){
|
if (!user.isAdmin) {
|
||||||
throw new Meteor.Error('not-allowed');
|
throw new Meteor.Error('not-allowed');
|
||||||
}
|
}
|
||||||
emails.forEach((email) => {
|
emails.forEach((email) => {
|
||||||
if (email && SimpleSchema.RegEx.Email.test(email)) {
|
if (email && SimpleSchema.RegEx.Email.test(email)) {
|
||||||
// Checks if the email is already link to an account.
|
// Checks if the email is already link to an account.
|
||||||
const userExist = Users.findOne({email});
|
const userExist = Users.findOne({ email });
|
||||||
if (userExist){
|
if (userExist) {
|
||||||
throw new Meteor.Error('user-exist', `The user with the email ${email} has already an account.`);
|
throw new Meteor.Error(
|
||||||
|
'user-exist',
|
||||||
|
`The user with the email ${email} has already an account.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Checks if the email is already link to an invitation.
|
// Checks if the email is already link to an invitation.
|
||||||
const invitation = InvitationCodes.findOne({email});
|
const invitation = InvitationCodes.findOne({ email });
|
||||||
if (invitation){
|
if (invitation) {
|
||||||
InvitationCodes.update(invitation, {$set : {boardsToBeInvited: boards}});
|
InvitationCodes.update(invitation, {
|
||||||
sendInvitationEmail(invitation._id);
|
$set: { boardsToBeInvited: boards },
|
||||||
}else {
|
|
||||||
const code = getRandomNum(100000, 999999);
|
|
||||||
InvitationCodes.insert({code, email, boardsToBeInvited: boards, createdAt: new Date(), authorId: Meteor.userId()}, function(err, _id){
|
|
||||||
if (!err && _id) {
|
|
||||||
sendInvitationEmail(_id);
|
|
||||||
} else {
|
|
||||||
throw new Meteor.Error('invitation-generated-fail', err.message);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
sendInvitationEmail(invitation._id);
|
||||||
|
} else {
|
||||||
|
const code = getRandomNum(100000, 999999);
|
||||||
|
InvitationCodes.insert(
|
||||||
|
{
|
||||||
|
code,
|
||||||
|
email,
|
||||||
|
boardsToBeInvited: boards,
|
||||||
|
createdAt: new Date(),
|
||||||
|
authorId: Meteor.userId(),
|
||||||
|
},
|
||||||
|
function(err, _id) {
|
||||||
|
if (!err && _id) {
|
||||||
|
sendInvitationEmail(_id);
|
||||||
|
} else {
|
||||||
|
throw new Meteor.Error(
|
||||||
|
'invitation-generated-fail',
|
||||||
|
err.message
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -215,11 +277,15 @@ if (Meteor.isServer) {
|
||||||
Email.send({
|
Email.send({
|
||||||
to: user.emails[0].address,
|
to: user.emails[0].address,
|
||||||
from: Accounts.emailTemplates.from,
|
from: Accounts.emailTemplates.from,
|
||||||
subject: TAPi18n.__('email-smtp-test-subject', {lng: lang}),
|
subject: TAPi18n.__('email-smtp-test-subject', { lng: lang }),
|
||||||
text: TAPi18n.__('email-smtp-test-text', {lng: lang}),
|
text: TAPi18n.__('email-smtp-test-text', { lng: lang }),
|
||||||
});
|
});
|
||||||
} catch ({message}) {
|
} catch ({ message }) {
|
||||||
throw new Meteor.Error('email-fail', `${TAPi18n.__('email-fail-text', {lng: lang})}: ${ message }`, message);
|
throw new Meteor.Error(
|
||||||
|
'email-fail',
|
||||||
|
`${TAPi18n.__('email-fail-text', { lng: lang })}: ${message}`,
|
||||||
|
message
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
message: 'email-sent',
|
message: 'email-sent',
|
||||||
|
|
@ -227,7 +293,7 @@ if (Meteor.isServer) {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getCustomUI(){
|
getCustomUI() {
|
||||||
const setting = Settings.findOne({});
|
const setting = Settings.findOne({});
|
||||||
if (!setting.productName) {
|
if (!setting.productName) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -240,7 +306,7 @@ if (Meteor.isServer) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getMatomoConf(){
|
getMatomoConf() {
|
||||||
return {
|
return {
|
||||||
address: getEnvVar('MATOMO_ADDRESS'),
|
address: getEnvVar('MATOMO_ADDRESS'),
|
||||||
siteId: getEnvVar('MATOMO_SITE_ID'),
|
siteId: getEnvVar('MATOMO_SITE_ID'),
|
||||||
|
|
@ -275,3 +341,5 @@ if (Meteor.isServer) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Settings;
|
||||||
|
|
|
||||||
|
|
@ -3,89 +3,125 @@ Swimlanes = new Mongo.Collection('swimlanes');
|
||||||
/**
|
/**
|
||||||
* A swimlane is an line in the kaban board.
|
* A swimlane is an line in the kaban board.
|
||||||
*/
|
*/
|
||||||
Swimlanes.attachSchema(new SimpleSchema({
|
Swimlanes.attachSchema(
|
||||||
title: {
|
new SimpleSchema({
|
||||||
/**
|
title: {
|
||||||
* the title of the swimlane
|
/**
|
||||||
*/
|
* the title of the swimlane
|
||||||
type: String,
|
*/
|
||||||
},
|
type: String,
|
||||||
archived: {
|
|
||||||
/**
|
|
||||||
* is the swimlane archived?
|
|
||||||
*/
|
|
||||||
type: Boolean,
|
|
||||||
autoValue() { // eslint-disable-line consistent-return
|
|
||||||
if (this.isInsert && !this.isSet) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
archived: {
|
||||||
boardId: {
|
/**
|
||||||
/**
|
* is the swimlane archived?
|
||||||
* the ID of the board the swimlane is attached to
|
*/
|
||||||
*/
|
type: Boolean,
|
||||||
type: String,
|
// eslint-disable-next-line consistent-return
|
||||||
},
|
autoValue() {
|
||||||
createdAt: {
|
if (this.isInsert && !this.isSet) {
|
||||||
/**
|
return false;
|
||||||
* creation date of the swimlane
|
}
|
||||||
*/
|
},
|
||||||
type: Date,
|
|
||||||
autoValue() { // eslint-disable-line consistent-return
|
|
||||||
if (this.isInsert) {
|
|
||||||
return new Date();
|
|
||||||
} else {
|
|
||||||
this.unset();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
boardId: {
|
||||||
sort: {
|
/**
|
||||||
/**
|
* the ID of the board the swimlane is attached to
|
||||||
* the sort value of the swimlane
|
*/
|
||||||
*/
|
type: String,
|
||||||
type: Number,
|
|
||||||
decimal: true,
|
|
||||||
// XXX We should probably provide a default
|
|
||||||
optional: true,
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
/**
|
|
||||||
* the color of the swimlane
|
|
||||||
*/
|
|
||||||
type: String,
|
|
||||||
optional: true,
|
|
||||||
// silver is the default, so it is left out
|
|
||||||
allowedValues: [
|
|
||||||
'white', 'green', 'yellow', 'orange', 'red', 'purple',
|
|
||||||
'blue', 'sky', 'lime', 'pink', 'black',
|
|
||||||
'peachpuff', 'crimson', 'plum', 'darkgreen',
|
|
||||||
'slateblue', 'magenta', 'gold', 'navy', 'gray',
|
|
||||||
'saddlebrown', 'paleturquoise', 'mistyrose', 'indigo',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
updatedAt: {
|
|
||||||
/**
|
|
||||||
* when was the swimlane last edited
|
|
||||||
*/
|
|
||||||
type: Date,
|
|
||||||
optional: true,
|
|
||||||
autoValue() { // eslint-disable-line consistent-return
|
|
||||||
if (this.isUpdate) {
|
|
||||||
return new Date();
|
|
||||||
} else {
|
|
||||||
this.unset();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
createdAt: {
|
||||||
type: {
|
/**
|
||||||
/**
|
* creation date of the swimlane
|
||||||
* The type of swimlane
|
*/
|
||||||
*/
|
type: Date,
|
||||||
type: String,
|
// eslint-disable-next-line consistent-return
|
||||||
defaultValue: 'swimlane',
|
autoValue() {
|
||||||
},
|
if (this.isInsert) {
|
||||||
}));
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sort: {
|
||||||
|
/**
|
||||||
|
* the sort value of the swimlane
|
||||||
|
*/
|
||||||
|
type: Number,
|
||||||
|
decimal: true,
|
||||||
|
// XXX We should probably provide a default
|
||||||
|
optional: true,
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
/**
|
||||||
|
* the color of the swimlane
|
||||||
|
*/
|
||||||
|
type: String,
|
||||||
|
optional: true,
|
||||||
|
// silver is the default, so it is left out
|
||||||
|
allowedValues: [
|
||||||
|
'white',
|
||||||
|
'green',
|
||||||
|
'yellow',
|
||||||
|
'orange',
|
||||||
|
'red',
|
||||||
|
'purple',
|
||||||
|
'blue',
|
||||||
|
'sky',
|
||||||
|
'lime',
|
||||||
|
'pink',
|
||||||
|
'black',
|
||||||
|
'peachpuff',
|
||||||
|
'crimson',
|
||||||
|
'plum',
|
||||||
|
'darkgreen',
|
||||||
|
'slateblue',
|
||||||
|
'magenta',
|
||||||
|
'gold',
|
||||||
|
'navy',
|
||||||
|
'gray',
|
||||||
|
'saddlebrown',
|
||||||
|
'paleturquoise',
|
||||||
|
'mistyrose',
|
||||||
|
'indigo',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
/**
|
||||||
|
* when was the swimlane last edited
|
||||||
|
*/
|
||||||
|
type: Date,
|
||||||
|
optional: true,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isUpdate || this.isUpsert || this.isInsert) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modifiedAt: {
|
||||||
|
type: Date,
|
||||||
|
denyUpdate: false,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert || this.isUpsert || this.isUpdate) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
/**
|
||||||
|
* The type of swimlane
|
||||||
|
*/
|
||||||
|
type: String,
|
||||||
|
defaultValue: 'swimlane',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
Swimlanes.allow({
|
Swimlanes.allow({
|
||||||
insert(userId, doc) {
|
insert(userId, doc) {
|
||||||
|
|
@ -109,7 +145,7 @@ Swimlanes.helpers({
|
||||||
const _id = Swimlanes.insert(this);
|
const _id = Swimlanes.insert(this);
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
swimlaneId: {$in: [oldId, '']},
|
swimlaneId: { $in: [oldId, ''] },
|
||||||
archived: false,
|
archived: false,
|
||||||
};
|
};
|
||||||
if (oldBoardId) {
|
if (oldBoardId) {
|
||||||
|
|
@ -126,18 +162,24 @@ Swimlanes.helpers({
|
||||||
},
|
},
|
||||||
|
|
||||||
cards() {
|
cards() {
|
||||||
return Cards.find(Filter.mongoSelector({
|
return Cards.find(
|
||||||
swimlaneId: this._id,
|
Filter.mongoSelector({
|
||||||
archived: false,
|
swimlaneId: this._id,
|
||||||
}), { sort: ['sort'] });
|
archived: false,
|
||||||
|
}),
|
||||||
|
{ sort: ['sort'] }
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
lists() {
|
lists() {
|
||||||
return Lists.find({
|
return Lists.find(
|
||||||
boardId: this.boardId,
|
{
|
||||||
swimlaneId: {$in: [this._id, '']},
|
boardId: this.boardId,
|
||||||
archived: false,
|
swimlaneId: { $in: [this._id, ''] },
|
||||||
}, { sort: ['sort'] });
|
archived: false,
|
||||||
|
},
|
||||||
|
{ sort: ['sort'] }
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
myLists() {
|
myLists() {
|
||||||
|
|
@ -153,8 +195,7 @@ Swimlanes.helpers({
|
||||||
},
|
},
|
||||||
|
|
||||||
colorClass() {
|
colorClass() {
|
||||||
if (this.color)
|
if (this.color) return this.color;
|
||||||
return this.color;
|
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -182,7 +223,7 @@ Swimlanes.helpers({
|
||||||
},
|
},
|
||||||
|
|
||||||
remove() {
|
remove() {
|
||||||
Swimlanes.remove({ _id: this._id});
|
Swimlanes.remove({ _id: this._id });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -221,10 +262,16 @@ Swimlanes.mutations({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Swimlanes.before.update((userId, doc, fieldNames, modifier, options) => {
|
||||||
|
modifier.$set = modifier.$set || {};
|
||||||
|
modifier.$set.modifiedAt = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
Swimlanes.hookOptions.after.update = { fetchPrevious: false };
|
Swimlanes.hookOptions.after.update = { fetchPrevious: false };
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
|
Swimlanes._collection._ensureIndex({ modifiedAt: -1 });
|
||||||
Swimlanes._collection._ensureIndex({ boardId: 1 });
|
Swimlanes._collection._ensureIndex({ boardId: 1 });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -239,18 +286,21 @@ if (Meteor.isServer) {
|
||||||
});
|
});
|
||||||
|
|
||||||
Swimlanes.before.remove(function(userId, doc) {
|
Swimlanes.before.remove(function(userId, doc) {
|
||||||
const lists = Lists.find({
|
const lists = Lists.find(
|
||||||
boardId: doc.boardId,
|
{
|
||||||
swimlaneId: {$in: [doc._id, '']},
|
boardId: doc.boardId,
|
||||||
archived: false,
|
swimlaneId: { $in: [doc._id, ''] },
|
||||||
}, { sort: ['sort'] });
|
archived: false,
|
||||||
|
},
|
||||||
|
{ sort: ['sort'] }
|
||||||
|
);
|
||||||
|
|
||||||
if (lists.count() < 2) {
|
if (lists.count() < 2) {
|
||||||
lists.forEach((list) => {
|
lists.forEach((list) => {
|
||||||
list.remove();
|
list.remove();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Cards.remove({swimlaneId: doc._id});
|
Cards.remove({ swimlaneId: doc._id });
|
||||||
}
|
}
|
||||||
|
|
||||||
Activities.insert({
|
Activities.insert({
|
||||||
|
|
@ -287,22 +337,23 @@ if (Meteor.isServer) {
|
||||||
* @return_type [{_id: string,
|
* @return_type [{_id: string,
|
||||||
* title: string}]
|
* title: string}]
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes', function (req, res) {
|
JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes', function(req, res) {
|
||||||
try {
|
try {
|
||||||
const paramBoardId = req.params.boardId;
|
const paramBoardId = req.params.boardId;
|
||||||
Authentication.checkBoardAccess( req.userId, paramBoardId);
|
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
||||||
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: Swimlanes.find({ boardId: paramBoardId, archived: false }).map(function (doc) {
|
data: Swimlanes.find({ boardId: paramBoardId, archived: false }).map(
|
||||||
return {
|
function(doc) {
|
||||||
_id: doc._id,
|
return {
|
||||||
title: doc.title,
|
_id: doc._id,
|
||||||
};
|
title: doc.title,
|
||||||
}),
|
};
|
||||||
|
}
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: error,
|
data: error,
|
||||||
|
|
@ -319,17 +370,23 @@ if (Meteor.isServer) {
|
||||||
* @param {string} swimlaneId the ID of the swimlane
|
* @param {string} swimlaneId the ID of the swimlane
|
||||||
* @return_type Swimlanes
|
* @return_type Swimlanes
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes/:swimlaneId', function (req, res) {
|
JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes/:swimlaneId', function(
|
||||||
|
req,
|
||||||
|
res
|
||||||
|
) {
|
||||||
try {
|
try {
|
||||||
const paramBoardId = req.params.boardId;
|
const paramBoardId = req.params.boardId;
|
||||||
const paramSwimlaneId = req.params.swimlaneId;
|
const paramSwimlaneId = req.params.swimlaneId;
|
||||||
Authentication.checkBoardAccess( req.userId, paramBoardId);
|
Authentication.checkBoardAccess(req.userId, paramBoardId);
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: Swimlanes.findOne({ _id: paramSwimlaneId, boardId: paramBoardId, archived: false }),
|
data: Swimlanes.findOne({
|
||||||
|
_id: paramSwimlaneId,
|
||||||
|
boardId: paramBoardId,
|
||||||
|
archived: false,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: error,
|
data: error,
|
||||||
|
|
@ -346,9 +403,9 @@ if (Meteor.isServer) {
|
||||||
* @param {string} title the new title of the swimlane
|
* @param {string} title the new title of the swimlane
|
||||||
* @return_type {_id: string}
|
* @return_type {_id: string}
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('POST', '/api/boards/:boardId/swimlanes', function (req, res) {
|
JsonRoutes.add('POST', '/api/boards/:boardId/swimlanes', function(req, res) {
|
||||||
try {
|
try {
|
||||||
Authentication.checkUserId( req.userId);
|
Authentication.checkUserId(req.userId);
|
||||||
const paramBoardId = req.params.boardId;
|
const paramBoardId = req.params.boardId;
|
||||||
const board = Boards.findOne(paramBoardId);
|
const board = Boards.findOne(paramBoardId);
|
||||||
const id = Swimlanes.insert({
|
const id = Swimlanes.insert({
|
||||||
|
|
@ -362,8 +419,7 @@ if (Meteor.isServer) {
|
||||||
_id: id,
|
_id: id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
} catch (error) {
|
||||||
catch (error) {
|
|
||||||
JsonRoutes.sendResult(res, {
|
JsonRoutes.sendResult(res, {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: error,
|
data: error,
|
||||||
|
|
@ -382,25 +438,29 @@ if (Meteor.isServer) {
|
||||||
* @param {string} swimlaneId the ID of the swimlane
|
* @param {string} swimlaneId the ID of the swimlane
|
||||||
* @return_type {_id: string}
|
* @return_type {_id: string}
|
||||||
*/
|
*/
|
||||||
JsonRoutes.add('DELETE', '/api/boards/:boardId/swimlanes/:swimlaneId', function (req, res) {
|
JsonRoutes.add(
|
||||||
try {
|
'DELETE',
|
||||||
Authentication.checkUserId( req.userId);
|
'/api/boards/:boardId/swimlanes/:swimlaneId',
|
||||||
const paramBoardId = req.params.boardId;
|
function(req, res) {
|
||||||
const paramSwimlaneId = req.params.swimlaneId;
|
try {
|
||||||
Swimlanes.remove({ _id: paramSwimlaneId, boardId: paramBoardId });
|
Authentication.checkUserId(req.userId);
|
||||||
JsonRoutes.sendResult(res, {
|
const paramBoardId = req.params.boardId;
|
||||||
code: 200,
|
const paramSwimlaneId = req.params.swimlaneId;
|
||||||
data: {
|
Swimlanes.remove({ _id: paramSwimlaneId, boardId: paramBoardId });
|
||||||
_id: paramSwimlaneId,
|
JsonRoutes.sendResult(res, {
|
||||||
},
|
code: 200,
|
||||||
});
|
data: {
|
||||||
|
_id: paramSwimlaneId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
JsonRoutes.sendResult(res, {
|
||||||
|
code: 200,
|
||||||
|
data: error,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (error) {
|
);
|
||||||
JsonRoutes.sendResult(res, {
|
|
||||||
code: 200,
|
|
||||||
data: error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Swimlanes;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { Meteor } from 'meteor/meteor';
|
||||||
|
|
||||||
Triggers = new Mongo.Collection('triggers');
|
Triggers = new Mongo.Collection('triggers');
|
||||||
|
|
||||||
Triggers.mutations({
|
Triggers.mutations({
|
||||||
|
|
@ -23,7 +25,6 @@ Triggers.allow({
|
||||||
});
|
});
|
||||||
|
|
||||||
Triggers.helpers({
|
Triggers.helpers({
|
||||||
|
|
||||||
description() {
|
description() {
|
||||||
return this.desc;
|
return this.desc;
|
||||||
},
|
},
|
||||||
|
|
@ -56,3 +57,16 @@ Triggers.helpers({
|
||||||
return cardLabels;
|
return cardLabels;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Triggers.before.update((userId, doc, fieldNames, modifier, options) => {
|
||||||
|
modifier.$set = modifier.$set || {};
|
||||||
|
modifier.$set.modifiedAt = Date.now();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Meteor.isServer) {
|
||||||
|
Meteor.startup(() => {
|
||||||
|
Triggers._collection._ensureIndex({ modifiedAt: -1 });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Triggers;
|
||||||
|
|
|
||||||
|
|
@ -2,31 +2,66 @@
|
||||||
// `UnsavedEdits` API on the client.
|
// `UnsavedEdits` API on the client.
|
||||||
UnsavedEditCollection = new Mongo.Collection('unsaved-edits');
|
UnsavedEditCollection = new Mongo.Collection('unsaved-edits');
|
||||||
|
|
||||||
UnsavedEditCollection.attachSchema(new SimpleSchema({
|
UnsavedEditCollection.attachSchema(
|
||||||
fieldName: {
|
new SimpleSchema({
|
||||||
type: String,
|
fieldName: {
|
||||||
},
|
type: String,
|
||||||
docId: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
value: {
|
|
||||||
type: String,
|
|
||||||
},
|
|
||||||
userId: {
|
|
||||||
type: String,
|
|
||||||
autoValue() { // eslint-disable-line consistent-return
|
|
||||||
if (this.isInsert && !this.isSet) {
|
|
||||||
return this.userId;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
docId: {
|
||||||
}));
|
type: String,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
userId: {
|
||||||
|
type: String,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert && !this.isSet) {
|
||||||
|
return this.userId;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: Date,
|
||||||
|
optional: true,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
modifiedAt: {
|
||||||
|
type: Date,
|
||||||
|
denyUpdate: false,
|
||||||
|
// eslint-disable-next-line consistent-return
|
||||||
|
autoValue() {
|
||||||
|
if (this.isInsert || this.isUpsert || this.isUpdate) {
|
||||||
|
return new Date();
|
||||||
|
} else {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
UnsavedEditCollection.before.update(
|
||||||
|
(userId, doc, fieldNames, modifier, options) => {
|
||||||
|
modifier.$set = modifier.$set || {};
|
||||||
|
modifier.$set.modifiedAt = Date.now();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
if (Meteor.isServer) {
|
||||||
function isAuthor(userId, doc, fieldNames = []) {
|
function isAuthor(userId, doc, fieldNames = []) {
|
||||||
return userId === doc.userId && fieldNames.indexOf('userId') === -1;
|
return userId === doc.userId && fieldNames.indexOf('userId') === -1;
|
||||||
}
|
}
|
||||||
Meteor.startup(() => {
|
Meteor.startup(() => {
|
||||||
|
UnsavedEditCollection._collection._ensureIndex({ modifiedAt: -1 });
|
||||||
UnsavedEditCollection._collection._ensureIndex({ userId: 1 });
|
UnsavedEditCollection._collection._ensureIndex({ userId: 1 });
|
||||||
});
|
});
|
||||||
UnsavedEditCollection.allow({
|
UnsavedEditCollection.allow({
|
||||||
|
|
@ -36,3 +71,5 @@ if (Meteor.isServer) {
|
||||||
fetch: ['userId'],
|
fetch: ['userId'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default UnsavedEditCollection;
|
||||||
|
|
|
||||||
883
models/users.js
883
models/users.js
File diff suppressed because it is too large
Load diff
34
package.json
34
package.json
|
|
@ -4,9 +4,29 @@
|
||||||
"description": "Open-Source kanban",
|
"description": "Open-Source kanban",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint --ignore-pattern 'packages/*' .",
|
"lint": "eslint --cache --ext .js --ignore-path .eslintignore .",
|
||||||
|
"lint:eslint:fix": "eslint --ext .js --ignore-path .eslintignore --fix .",
|
||||||
|
"lint:staged": "lint-staged",
|
||||||
|
"prettify": "prettier --write '**/*.js' '**/*.jsx'",
|
||||||
"test": "npm run --silent lint"
|
"test": "npm run --silent lint"
|
||||||
},
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.js": [
|
||||||
|
"meteor npm run prettify",
|
||||||
|
"meteor npm run lint:eslint:fix",
|
||||||
|
"git add --force"
|
||||||
|
],
|
||||||
|
"*.jsx": [
|
||||||
|
"meteor npm run prettify",
|
||||||
|
"meteor npm run lint:eslint:fix",
|
||||||
|
"git add --force"
|
||||||
|
],
|
||||||
|
"*.json": [
|
||||||
|
"prettier --write",
|
||||||
|
"git add --force"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"pre-commit": "lint:staged",
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": "@meteorjs/eslint-config-meteor"
|
"extends": "@meteorjs/eslint-config-meteor"
|
||||||
},
|
},
|
||||||
|
|
@ -20,7 +40,17 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://wekan.github.io",
|
"homepage": "https://wekan.github.io",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^5.16.0"
|
"eslint": "^5.16.0",
|
||||||
|
"eslint-config-meteor": "0.0.9",
|
||||||
|
"eslint-config-prettier": "^3.6.0",
|
||||||
|
"eslint-import-resolver-meteor": "^0.4.0",
|
||||||
|
"eslint-plugin-import": "^2.18.0",
|
||||||
|
"eslint-plugin-meteor": "^4.2.2",
|
||||||
|
"eslint-plugin-prettier": "^3.1.0",
|
||||||
|
"lint-staged": "^7.3.0",
|
||||||
|
"pre-commit": "^1.2.2",
|
||||||
|
"prettier": "^1.18.2",
|
||||||
|
"prettier-eslint": "^8.8.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.4.3",
|
"@babel/runtime": "^7.4.3",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,23 @@
|
||||||
|
import AccountSettings from '../models/accountSettings';
|
||||||
|
import Actions from '../models/actions';
|
||||||
|
import Activities from '../models/activities';
|
||||||
|
import Announcements from '../models/announcements';
|
||||||
|
import Boards from '../models/boards';
|
||||||
|
import CardComments from '../models/cardComments';
|
||||||
|
import Cards from '../models/cards';
|
||||||
|
import ChecklistItems from '../models/checklistItems';
|
||||||
|
import Checklists from '../models/checklists';
|
||||||
|
import CustomFields from '../models/customFields';
|
||||||
|
import Integrations from '../models/integrations';
|
||||||
|
import InvitationCodes from '../models/invitationCodes';
|
||||||
|
import Lists from '../models/lists';
|
||||||
|
import Rules from '../models/rules';
|
||||||
|
import Settings from '../models/settings';
|
||||||
|
import Swimlanes from '../models/swimlanes';
|
||||||
|
import Triggers from '../models/triggers';
|
||||||
|
import UnsavedEdits from '../models/unsavedEdits';
|
||||||
|
import Users from '../models/users';
|
||||||
|
|
||||||
// Anytime you change the schema of one of the collection in a non-backward
|
// Anytime you change the schema of one of the collection in a non-backward
|
||||||
// compatible way you have to write a migration in this file using the following
|
// compatible way you have to write a migration in this file using the following
|
||||||
// API:
|
// API:
|
||||||
|
|
@ -28,18 +48,22 @@ const noValidateMulti = { ...noValidate, multi: true };
|
||||||
|
|
||||||
Migrations.add('board-background-color', () => {
|
Migrations.add('board-background-color', () => {
|
||||||
const defaultColor = '#16A085';
|
const defaultColor = '#16A085';
|
||||||
Boards.update({
|
Boards.update(
|
||||||
background: {
|
{
|
||||||
$exists: false,
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
$set: {
|
|
||||||
background: {
|
background: {
|
||||||
type: 'color',
|
$exists: false,
|
||||||
color: defaultColor,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
{
|
||||||
|
$set: {
|
||||||
|
background: {
|
||||||
|
type: 'color',
|
||||||
|
color: defaultColor,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
noValidateMulti
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('lowercase-board-permission', () => {
|
Migrations.add('lowercase-board-permission', () => {
|
||||||
|
|
@ -57,24 +81,28 @@ Migrations.add('change-attachments-type-for-non-images', () => {
|
||||||
const newTypeForNonImage = 'application/octet-stream';
|
const newTypeForNonImage = 'application/octet-stream';
|
||||||
Attachments.find().forEach((file) => {
|
Attachments.find().forEach((file) => {
|
||||||
if (!file.isImage()) {
|
if (!file.isImage()) {
|
||||||
Attachments.update(file._id, {
|
Attachments.update(
|
||||||
$set: {
|
file._id,
|
||||||
'original.type': newTypeForNonImage,
|
{
|
||||||
'copies.attachments.type': newTypeForNonImage,
|
$set: {
|
||||||
|
'original.type': newTypeForNonImage,
|
||||||
|
'copies.attachments.type': newTypeForNonImage,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidate);
|
noValidate
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('card-covers', () => {
|
Migrations.add('card-covers', () => {
|
||||||
Cards.find().forEach((card) => {
|
Cards.find().forEach((card) => {
|
||||||
const cover = Attachments.findOne({ cardId: card._id, cover: true });
|
const cover = Attachments.findOne({ cardId: card._id, cover: true });
|
||||||
if (cover) {
|
if (cover) {
|
||||||
Cards.update(card._id, {$set: {coverId: cover._id}}, noValidate);
|
Cards.update(card._id, { $set: { coverId: cover._id } }, noValidate);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Attachments.update({}, {$unset: {cover: ''}}, noValidateMulti);
|
Attachments.update({}, { $unset: { cover: '' } }, noValidateMulti);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('use-css-class-for-boards-colors', () => {
|
Migrations.add('use-css-class-for-boards-colors', () => {
|
||||||
|
|
@ -89,26 +117,31 @@ Migrations.add('use-css-class-for-boards-colors', () => {
|
||||||
Boards.find().forEach((board) => {
|
Boards.find().forEach((board) => {
|
||||||
const oldBoardColor = board.background.color;
|
const oldBoardColor = board.background.color;
|
||||||
const newBoardColor = associationTable[oldBoardColor];
|
const newBoardColor = associationTable[oldBoardColor];
|
||||||
Boards.update(board._id, {
|
Boards.update(
|
||||||
$set: { color: newBoardColor },
|
board._id,
|
||||||
$unset: { background: '' },
|
{
|
||||||
}, noValidate);
|
$set: { color: newBoardColor },
|
||||||
|
$unset: { background: '' },
|
||||||
|
},
|
||||||
|
noValidate
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('denormalize-star-number-per-board', () => {
|
Migrations.add('denormalize-star-number-per-board', () => {
|
||||||
Boards.find().forEach((board) => {
|
Boards.find().forEach((board) => {
|
||||||
const nStars = Users.find({'profile.starredBoards': board._id}).count();
|
const nStars = Users.find({ 'profile.starredBoards': board._id }).count();
|
||||||
Boards.update(board._id, {$set: {stars: nStars}}, noValidate);
|
Boards.update(board._id, { $set: { stars: nStars } }, noValidate);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// We want to keep a trace of former members so we can efficiently publish their
|
// We want to keep a trace of former members so we can efficiently publish their
|
||||||
// infos in the general board publication.
|
// infos in the general board publication.
|
||||||
Migrations.add('add-member-isactive-field', () => {
|
Migrations.add('add-member-isactive-field', () => {
|
||||||
Boards.find({}, {fields: {members: 1}}).forEach((board) => {
|
Boards.find({}, { fields: { members: 1 } }).forEach((board) => {
|
||||||
const allUsersWithSomeActivity = _.chain(
|
const allUsersWithSomeActivity = _.chain(
|
||||||
Activities.find({ boardId: board._id }, { fields:{ userId:1 }}).fetch())
|
Activities.find({ boardId: board._id }, { fields: { userId: 1 } }).fetch()
|
||||||
|
)
|
||||||
.pluck('userId')
|
.pluck('userId')
|
||||||
.uniq()
|
.uniq()
|
||||||
.value();
|
.value();
|
||||||
|
|
@ -127,7 +160,7 @@ Migrations.add('add-member-isactive-field', () => {
|
||||||
isActive: false,
|
isActive: false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
Boards.update(board._id, {$set: {members: newMemberSet}}, noValidate);
|
Boards.update(board._id, { $set: { members: newMemberSet } }, noValidate);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -184,7 +217,7 @@ Migrations.add('add-checklist-items', () => {
|
||||||
// Create new items
|
// Create new items
|
||||||
_.sortBy(checklist.items, 'sort').forEach((item, index) => {
|
_.sortBy(checklist.items, 'sort').forEach((item, index) => {
|
||||||
ChecklistItems.direct.insert({
|
ChecklistItems.direct.insert({
|
||||||
title: (item.title ? item.title : 'Checklist'),
|
title: item.title ? item.title : 'Checklist',
|
||||||
sort: index,
|
sort: index,
|
||||||
isFinished: item.isFinished,
|
isFinished: item.isFinished,
|
||||||
checklistId: checklist._id,
|
checklistId: checklist._id,
|
||||||
|
|
@ -193,8 +226,9 @@ Migrations.add('add-checklist-items', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Delete old ones
|
// Delete old ones
|
||||||
Checklists.direct.update({ _id: checklist._id },
|
Checklists.direct.update(
|
||||||
{ $unset: { items : 1 } },
|
{ _id: checklist._id },
|
||||||
|
{ $unset: { items: 1 } },
|
||||||
noValidate
|
noValidate
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -217,324 +251,512 @@ Migrations.add('add-card-types', () => {
|
||||||
Cards.find().forEach((card) => {
|
Cards.find().forEach((card) => {
|
||||||
Cards.direct.update(
|
Cards.direct.update(
|
||||||
{ _id: card._id },
|
{ _id: card._id },
|
||||||
{ $set: {
|
{
|
||||||
type: 'cardType-card',
|
$set: {
|
||||||
linkedId: null } },
|
type: 'cardType-card',
|
||||||
|
linkedId: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
noValidate
|
noValidate
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('add-custom-fields-to-cards', () => {
|
Migrations.add('add-custom-fields-to-cards', () => {
|
||||||
Cards.update({
|
Cards.update(
|
||||||
customFields: {
|
{
|
||||||
$exists: false,
|
customFields: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
$set: {
|
$set: {
|
||||||
customFields:[],
|
customFields: [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('add-requester-field', () => {
|
Migrations.add('add-requester-field', () => {
|
||||||
Cards.update({
|
Cards.update(
|
||||||
requestedBy: {
|
{
|
||||||
$exists: false,
|
requestedBy: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
$set: {
|
$set: {
|
||||||
requestedBy:'',
|
requestedBy: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('add-assigner-field', () => {
|
Migrations.add('add-assigner-field', () => {
|
||||||
Cards.update({
|
Cards.update(
|
||||||
assignedBy: {
|
{
|
||||||
$exists: false,
|
assignedBy: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
$set: {
|
$set: {
|
||||||
assignedBy:'',
|
assignedBy: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('add-parent-field-to-cards', () => {
|
Migrations.add('add-parent-field-to-cards', () => {
|
||||||
Cards.update({
|
Cards.update(
|
||||||
parentId: {
|
{
|
||||||
$exists: false,
|
parentId: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
$set: {
|
$set: {
|
||||||
parentId:'',
|
parentId: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('add-subtasks-boards', () => {
|
Migrations.add('add-subtasks-boards', () => {
|
||||||
Boards.update({
|
Boards.update(
|
||||||
subtasksDefaultBoardId: {
|
{
|
||||||
$exists: false,
|
subtasksDefaultBoardId: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
$set: {
|
$set: {
|
||||||
subtasksDefaultBoardId: null,
|
subtasksDefaultBoardId: null,
|
||||||
subtasksDefaultListId: null,
|
subtasksDefaultListId: null,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('add-subtasks-sort', () => {
|
Migrations.add('add-subtasks-sort', () => {
|
||||||
Boards.update({
|
Boards.update(
|
||||||
subtaskSort: {
|
{
|
||||||
$exists: false,
|
subtaskSort: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
$set: {
|
$set: {
|
||||||
subtaskSort: -1,
|
subtaskSort: -1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('add-subtasks-allowed', () => {
|
Migrations.add('add-subtasks-allowed', () => {
|
||||||
Boards.update({
|
Boards.update(
|
||||||
allowsSubtasks: {
|
{
|
||||||
$exists: false,
|
allowsSubtasks: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
$set: {
|
$set: {
|
||||||
allowsSubtasks: true,
|
allowsSubtasks: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('add-subtasks-allowed', () => {
|
Migrations.add('add-subtasks-allowed', () => {
|
||||||
Boards.update({
|
Boards.update(
|
||||||
presentParentTask: {
|
{
|
||||||
$exists: false,
|
presentParentTask: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
$set: {
|
$set: {
|
||||||
presentParentTask: 'no-parent',
|
presentParentTask: 'no-parent',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('add-authenticationMethod', () => {
|
Migrations.add('add-authenticationMethod', () => {
|
||||||
Users.update({
|
Users.update(
|
||||||
'authenticationMethod': {
|
{
|
||||||
$exists: false,
|
authenticationMethod: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
$set: {
|
$set: {
|
||||||
'authenticationMethod': 'password',
|
authenticationMethod: 'password',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('remove-tag', () => {
|
Migrations.add('remove-tag', () => {
|
||||||
Users.update({
|
Users.update(
|
||||||
}, {
|
{},
|
||||||
$unset: {
|
{
|
||||||
'profile.tags':1,
|
$unset: {
|
||||||
|
'profile.tags': 1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('remove-customFields-references-broken', () => {
|
Migrations.add('remove-customFields-references-broken', () => {
|
||||||
Cards.update({'customFields.$value': null},
|
Cards.update(
|
||||||
{ $pull: {
|
{ 'customFields.$value': null },
|
||||||
customFields: {value: null},
|
{
|
||||||
|
$pull: {
|
||||||
|
customFields: { value: null },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('add-product-name', () => {
|
Migrations.add('add-product-name', () => {
|
||||||
Settings.update({
|
Settings.update(
|
||||||
productName: {
|
{
|
||||||
$exists: false,
|
productName: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
$set: {
|
$set: {
|
||||||
productName:'',
|
productName: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('add-hide-logo', () => {
|
Migrations.add('add-hide-logo', () => {
|
||||||
Settings.update({
|
Settings.update(
|
||||||
hideLogo: {
|
{
|
||||||
$exists: false,
|
hideLogo: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
$set: {
|
$set: {
|
||||||
hideLogo: false,
|
hideLogo: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('add-custom-html-after-body-start', () => {
|
Migrations.add('add-custom-html-after-body-start', () => {
|
||||||
Settings.update({
|
Settings.update(
|
||||||
customHTMLafterBodyStart: {
|
{
|
||||||
$exists: false,
|
customHTMLafterBodyStart: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
$set: {
|
$set: {
|
||||||
customHTMLafterBodyStart:'',
|
customHTMLafterBodyStart: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('add-custom-html-before-body-end', () => {
|
Migrations.add('add-custom-html-before-body-end', () => {
|
||||||
Settings.update({
|
Settings.update(
|
||||||
customHTMLbeforeBodyEnd: {
|
{
|
||||||
$exists: false,
|
customHTMLbeforeBodyEnd: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
$set: {
|
$set: {
|
||||||
customHTMLbeforeBodyEnd:'',
|
customHTMLbeforeBodyEnd: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('add-displayAuthenticationMethod', () => {
|
Migrations.add('add-displayAuthenticationMethod', () => {
|
||||||
Settings.update({
|
Settings.update(
|
||||||
displayAuthenticationMethod: {
|
{
|
||||||
$exists: false,
|
displayAuthenticationMethod: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
$set: {
|
$set: {
|
||||||
displayAuthenticationMethod: true,
|
displayAuthenticationMethod: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('add-defaultAuthenticationMethod', () => {
|
Migrations.add('add-defaultAuthenticationMethod', () => {
|
||||||
Settings.update({
|
Settings.update(
|
||||||
defaultAuthenticationMethod: {
|
{
|
||||||
$exists: false,
|
defaultAuthenticationMethod: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
$set: {
|
$set: {
|
||||||
defaultAuthenticationMethod: 'password',
|
defaultAuthenticationMethod: 'password',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('add-templates', () => {
|
Migrations.add('add-templates', () => {
|
||||||
Boards.update({
|
Boards.update(
|
||||||
type: {
|
{
|
||||||
$exists: false,
|
type: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
$set: {
|
$set: {
|
||||||
type: 'board',
|
type: 'board',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
Swimlanes.update({
|
);
|
||||||
type: {
|
Swimlanes.update(
|
||||||
$exists: false,
|
{
|
||||||
|
type: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
{
|
||||||
$set: {
|
$set: {
|
||||||
type: 'swimlane',
|
type: 'swimlane',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, noValidateMulti);
|
noValidateMulti
|
||||||
Lists.update({
|
);
|
||||||
type: {
|
Lists.update(
|
||||||
$exists: false,
|
{
|
||||||
|
type: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
|
swimlaneId: {
|
||||||
|
$exists: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
swimlaneId: {
|
{
|
||||||
$exists: false,
|
$set: {
|
||||||
|
type: 'list',
|
||||||
|
swimlaneId: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}, {
|
noValidateMulti
|
||||||
$set: {
|
);
|
||||||
type: 'list',
|
|
||||||
swimlaneId: '',
|
|
||||||
},
|
|
||||||
}, noValidateMulti);
|
|
||||||
Users.find({
|
Users.find({
|
||||||
'profile.templatesBoardId': {
|
'profile.templatesBoardId': {
|
||||||
$exists: false,
|
$exists: false,
|
||||||
},
|
},
|
||||||
}).forEach((user) => {
|
}).forEach((user) => {
|
||||||
// Create board and swimlanes
|
// Create board and swimlanes
|
||||||
Boards.insert({
|
Boards.insert(
|
||||||
title: TAPi18n.__('templates'),
|
{
|
||||||
permission: 'private',
|
title: TAPi18n.__('templates'),
|
||||||
type: 'template-container',
|
permission: 'private',
|
||||||
members: [
|
|
||||||
{
|
|
||||||
userId: user._id,
|
|
||||||
isAdmin: true,
|
|
||||||
isActive: true,
|
|
||||||
isNoComments: false,
|
|
||||||
isCommentOnly: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}, (err, boardId) => {
|
|
||||||
|
|
||||||
// Insert the reference to our templates board
|
|
||||||
Users.update(user._id, {$set: {'profile.templatesBoardId': boardId}});
|
|
||||||
|
|
||||||
// Insert the card templates swimlane
|
|
||||||
Swimlanes.insert({
|
|
||||||
title: TAPi18n.__('card-templates-swimlane'),
|
|
||||||
boardId,
|
|
||||||
sort: 1,
|
|
||||||
type: 'template-container',
|
type: 'template-container',
|
||||||
}, (err, swimlaneId) => {
|
members: [
|
||||||
|
{
|
||||||
|
userId: user._id,
|
||||||
|
isAdmin: true,
|
||||||
|
isActive: true,
|
||||||
|
isNoComments: false,
|
||||||
|
isCommentOnly: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
(err, boardId) => {
|
||||||
|
// Insert the reference to our templates board
|
||||||
|
Users.update(user._id, {
|
||||||
|
$set: { 'profile.templatesBoardId': boardId },
|
||||||
|
});
|
||||||
|
|
||||||
// Insert the reference to out card templates swimlane
|
// Insert the card templates swimlane
|
||||||
Users.update(user._id, {$set: {'profile.cardTemplatesSwimlaneId': swimlaneId}});
|
Swimlanes.insert(
|
||||||
});
|
{
|
||||||
|
title: TAPi18n.__('card-templates-swimlane'),
|
||||||
|
boardId,
|
||||||
|
sort: 1,
|
||||||
|
type: 'template-container',
|
||||||
|
},
|
||||||
|
(err, swimlaneId) => {
|
||||||
|
// Insert the reference to out card templates swimlane
|
||||||
|
Users.update(user._id, {
|
||||||
|
$set: { 'profile.cardTemplatesSwimlaneId': swimlaneId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Insert the list templates swimlane
|
// Insert the list templates swimlane
|
||||||
Swimlanes.insert({
|
Swimlanes.insert(
|
||||||
title: TAPi18n.__('list-templates-swimlane'),
|
{
|
||||||
boardId,
|
title: TAPi18n.__('list-templates-swimlane'),
|
||||||
sort: 2,
|
boardId,
|
||||||
type: 'template-container',
|
sort: 2,
|
||||||
}, (err, swimlaneId) => {
|
type: 'template-container',
|
||||||
|
},
|
||||||
|
(err, swimlaneId) => {
|
||||||
|
// Insert the reference to out list templates swimlane
|
||||||
|
Users.update(user._id, {
|
||||||
|
$set: { 'profile.listTemplatesSwimlaneId': swimlaneId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Insert the reference to out list templates swimlane
|
// Insert the board templates swimlane
|
||||||
Users.update(user._id, {$set: {'profile.listTemplatesSwimlaneId': swimlaneId}});
|
Swimlanes.insert(
|
||||||
});
|
{
|
||||||
|
title: TAPi18n.__('board-templates-swimlane'),
|
||||||
// Insert the board templates swimlane
|
boardId,
|
||||||
Swimlanes.insert({
|
sort: 3,
|
||||||
title: TAPi18n.__('board-templates-swimlane'),
|
type: 'template-container',
|
||||||
boardId,
|
},
|
||||||
sort: 3,
|
(err, swimlaneId) => {
|
||||||
type: 'template-container',
|
// Insert the reference to out board templates swimlane
|
||||||
}, (err, swimlaneId) => {
|
Users.update(user._id, {
|
||||||
|
$set: { 'profile.boardTemplatesSwimlaneId': swimlaneId },
|
||||||
// Insert the reference to out board templates swimlane
|
});
|
||||||
Users.update(user._id, {$set: {'profile.boardTemplatesSwimlaneId': swimlaneId}});
|
}
|
||||||
});
|
);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('fix-circular-reference_', () => {
|
Migrations.add('fix-circular-reference_', () => {
|
||||||
Cards.find().forEach((card) => {
|
Cards.find().forEach((card) => {
|
||||||
if (card.parentId === card._id) {
|
if (card.parentId === card._id) {
|
||||||
Cards.update(card._id, {$set: {parentId: ''}}, noValidateMulti);
|
Cards.update(card._id, { $set: { parentId: '' } }, noValidateMulti);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Migrations.add('mutate-boardIds-in-customfields', () => {
|
Migrations.add('mutate-boardIds-in-customfields', () => {
|
||||||
CustomFields.find().forEach((cf) => {
|
CustomFields.find().forEach((cf) => {
|
||||||
CustomFields.update(cf, {
|
CustomFields.update(
|
||||||
$set: {
|
cf,
|
||||||
boardIds: [cf.boardId],
|
{
|
||||||
|
$set: {
|
||||||
|
boardIds: [cf.boardId],
|
||||||
|
},
|
||||||
|
$unset: {
|
||||||
|
boardId: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
$unset: {
|
noValidateMulti
|
||||||
boardId: '',
|
);
|
||||||
},
|
|
||||||
}, noValidateMulti);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const firstBatchOfDbsToAddCreatedAndUpdated = [
|
||||||
|
AccountSettings,
|
||||||
|
Actions,
|
||||||
|
Activities,
|
||||||
|
Announcements,
|
||||||
|
Boards,
|
||||||
|
CardComments,
|
||||||
|
Cards,
|
||||||
|
ChecklistItems,
|
||||||
|
Checklists,
|
||||||
|
CustomFields,
|
||||||
|
Integrations,
|
||||||
|
InvitationCodes,
|
||||||
|
Lists,
|
||||||
|
Rules,
|
||||||
|
Settings,
|
||||||
|
Swimlanes,
|
||||||
|
Triggers,
|
||||||
|
UnsavedEdits,
|
||||||
|
];
|
||||||
|
|
||||||
|
firstBatchOfDbsToAddCreatedAndUpdated.forEach((db) => {
|
||||||
|
db.before.insert((userId, doc) => {
|
||||||
|
doc.createdAt = Date.now();
|
||||||
|
doc.updatedAt = doc.createdAt;
|
||||||
|
});
|
||||||
|
|
||||||
|
db.before.update((userId, doc, fieldNames, modifier, options) => {
|
||||||
|
modifier.$set = modifier.$set || {};
|
||||||
|
modifier.$set.updatedAt = new Date();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const modifiedAtTables = [
|
||||||
|
AccountSettings,
|
||||||
|
Actions,
|
||||||
|
Activities,
|
||||||
|
Announcements,
|
||||||
|
Boards,
|
||||||
|
CardComments,
|
||||||
|
Cards,
|
||||||
|
ChecklistItems,
|
||||||
|
Checklists,
|
||||||
|
CustomFields,
|
||||||
|
Integrations,
|
||||||
|
InvitationCodes,
|
||||||
|
Lists,
|
||||||
|
Rules,
|
||||||
|
Settings,
|
||||||
|
Swimlanes,
|
||||||
|
Triggers,
|
||||||
|
UnsavedEdits,
|
||||||
|
Users,
|
||||||
|
];
|
||||||
|
|
||||||
|
Migrations.add('add-missing-created-and-modified', () => {
|
||||||
|
Promise.all(
|
||||||
|
modifiedAtTables.map((db) =>
|
||||||
|
db
|
||||||
|
.rawCollection()
|
||||||
|
.update(
|
||||||
|
{ modifiedAt: { $exists: false } },
|
||||||
|
{ $set: { modifiedAt: new Date() } },
|
||||||
|
{ multi: true }
|
||||||
|
)
|
||||||
|
.then(() =>
|
||||||
|
db
|
||||||
|
.rawCollection()
|
||||||
|
.update(
|
||||||
|
{ createdAt: { $exists: false } },
|
||||||
|
{ $set: { createdAt: new Date() } },
|
||||||
|
{ multi: true }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.info('Successfully added createdAt and updatedAt to all tables');
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(e);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue