mirror of
https://github.com/wekan/wekan.git
synced 2026-01-24 18:26:10 +01:00
merge master changes
This commit is contained in:
commit
5df5c7f5d7
401 changed files with 142692 additions and 15007 deletions
|
|
@ -51,7 +51,7 @@ AccountSettings.allow({
|
|||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(() => {
|
||||
AccountSettings._collection._ensureIndex({ modifiedAt: -1 });
|
||||
AccountSettings._collection.createIndex({ modifiedAt: -1 });
|
||||
AccountSettings.upsert(
|
||||
{ _id: 'accounts-allowEmailChange' },
|
||||
{
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ Actions.helpers({
|
|||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(() => {
|
||||
Actions._collection._ensureIndex({ modifiedAt: -1 });
|
||||
Actions._collection.createIndex({ modifiedAt: -1 });
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,19 +82,19 @@ if (Meteor.isServer) {
|
|||
// creation in conjunction with the card or board id, as corresponding views
|
||||
// are largely used in the App. See #524.
|
||||
Meteor.startup(() => {
|
||||
Activities._collection._ensureIndex({ createdAt: -1 });
|
||||
Activities._collection._ensureIndex({ modifiedAt: -1 });
|
||||
Activities._collection._ensureIndex({ cardId: 1, createdAt: -1 });
|
||||
Activities._collection._ensureIndex({ boardId: 1, createdAt: -1 });
|
||||
Activities._collection._ensureIndex(
|
||||
Activities._collection.createIndex({ createdAt: -1 });
|
||||
Activities._collection.createIndex({ modifiedAt: -1 });
|
||||
Activities._collection.createIndex({ cardId: 1, createdAt: -1 });
|
||||
Activities._collection.createIndex({ boardId: 1, createdAt: -1 });
|
||||
Activities._collection.createIndex(
|
||||
{ commentId: 1 },
|
||||
{ partialFilterExpression: { commentId: { $exists: true } } },
|
||||
);
|
||||
Activities._collection._ensureIndex(
|
||||
Activities._collection.createIndex(
|
||||
{ attachmentId: 1 },
|
||||
{ partialFilterExpression: { attachmentId: { $exists: true } } },
|
||||
);
|
||||
Activities._collection._ensureIndex(
|
||||
Activities._collection.createIndex(
|
||||
{ customFieldId: 1 },
|
||||
{ partialFilterExpression: { customFieldId: { $exists: true } } },
|
||||
);
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ Announcements.allow({
|
|||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(() => {
|
||||
Announcements._collection._ensureIndex({ modifiedAt: -1 });
|
||||
Announcements._collection.createIndex({ modifiedAt: -1 });
|
||||
const announcements = Announcements.findOne({});
|
||||
if (!announcements) {
|
||||
Announcements.insert({ enabled: false, sort: 0 });
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { createBucket } from './lib/grid/createBucket';
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { AttachmentStoreStrategyFilesystem, AttachmentStoreStrategyGridFs} from '/models/lib/attachmentStoreStrategy';
|
||||
import FileStoreStrategyFactory, {moveToStorage, STORAGE_NAME_FILESYSTEM, STORAGE_NAME_GRIDFS} from '/models/lib/fileStoreStrategy';
|
||||
import FileStoreStrategyFactory, {moveToStorage, rename, STORAGE_NAME_FILESYSTEM, STORAGE_NAME_GRIDFS} from '/models/lib/fileStoreStrategy';
|
||||
|
||||
let attachmentBucket;
|
||||
let storagePath;
|
||||
|
|
@ -34,12 +34,13 @@ Attachments = new FilesCollection({
|
|||
return ret;
|
||||
},
|
||||
onAfterUpload(fileObj) {
|
||||
let storage = fileObj.meta.copyStorage || STORAGE_NAME_GRIDFS;
|
||||
// current storage is the filesystem, update object and database
|
||||
Object.keys(fileObj.versions).forEach(versionName => {
|
||||
fileObj.versions[versionName].storage = STORAGE_NAME_FILESYSTEM;
|
||||
});
|
||||
Attachments.update({ _id: fileObj._id }, { $set: { "versions" : fileObj.versions } });
|
||||
moveToStorage(fileObj, STORAGE_NAME_GRIDFS, fileStoreStrategyFactory);
|
||||
moveToStorage(fileObj, storage, fileStoreStrategyFactory);
|
||||
},
|
||||
interceptDownload(http, fileObj, versionName) {
|
||||
const ret = fileStoreStrategyFactory.getFileStrategy(fileObj, versionName).interceptDownload(http, this.cacheControl);
|
||||
|
|
@ -86,10 +87,17 @@ if (Meteor.isServer) {
|
|||
const fileObj = Attachments.findOne({_id: fileObjId});
|
||||
moveToStorage(fileObj, storageDestination, fileStoreStrategyFactory);
|
||||
},
|
||||
renameAttachment(fileObjId, newName) {
|
||||
check(fileObjId, String);
|
||||
check(newName, String);
|
||||
|
||||
const fileObj = Attachments.findOne({_id: fileObjId});
|
||||
rename(fileObj, newName, fileStoreStrategyFactory);
|
||||
},
|
||||
});
|
||||
|
||||
Meteor.startup(() => {
|
||||
Attachments.collection._ensureIndex({ 'meta.cardId': 1 });
|
||||
Attachments.collection.createIndex({ 'meta.cardId': 1 });
|
||||
const storagePath = fileStoreStrategyFactory.storagePath;
|
||||
if (!fs.existsSync(storagePath)) {
|
||||
console.log("create storagePath because it doesn't exist: " + storagePath);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import escapeForRegex from 'escape-string-regexp';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import {
|
||||
ALLOWED_BOARD_COLORS,
|
||||
ALLOWED_COLORS,
|
||||
|
|
@ -7,7 +9,7 @@ import {
|
|||
} from '/config/const';
|
||||
import Users from "./users";
|
||||
|
||||
const escapeForRegex = require('escape-string-regexp');
|
||||
// const escapeForRegex = require('escape-string-regexp');
|
||||
|
||||
Boards = new Mongo.Collection('boards');
|
||||
|
||||
|
|
@ -1737,15 +1739,15 @@ Boards.before.insert((userId, doc) => {
|
|||
if (Meteor.isServer) {
|
||||
// Let MongoDB ensure that a member is not included twice in the same board
|
||||
Meteor.startup(() => {
|
||||
Boards._collection._ensureIndex({ modifiedAt: -1 });
|
||||
Boards._collection._ensureIndex(
|
||||
Boards._collection.createIndex({ modifiedAt: -1 });
|
||||
Boards._collection.createIndex(
|
||||
{
|
||||
_id: 1,
|
||||
'members.userId': 1,
|
||||
},
|
||||
{ unique: true },
|
||||
);
|
||||
Boards._collection._ensureIndex({ 'members.userId': 1 });
|
||||
Boards._collection.createIndex({ 'members.userId': 1 });
|
||||
});
|
||||
|
||||
// Genesis: the first activity of the newly created board
|
||||
|
|
|
|||
|
|
@ -54,6 +54,6 @@ CardCommentReactions.allow({
|
|||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(() => {
|
||||
CardCommentReactions._collection._ensureIndex({ cardCommentId: 1 }, { unique: true });
|
||||
CardCommentReactions._collection.createIndex({ cardCommentId: 1 }, { unique: true });
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const escapeForRegex = require('escape-string-regexp');
|
||||
import escapeForRegex from 'escape-string-regexp';
|
||||
|
||||
CardComments = new Mongo.Collection('card_comments');
|
||||
|
||||
/**
|
||||
|
|
@ -178,8 +179,8 @@ if (Meteor.isServer) {
|
|||
// Comments are often fetched within a card, so we create an index to make these
|
||||
// queries more efficient.
|
||||
Meteor.startup(() => {
|
||||
CardComments._collection._ensureIndex({ modifiedAt: -1 });
|
||||
CardComments._collection._ensureIndex({ cardId: 1, createdAt: -1 });
|
||||
CardComments._collection.createIndex({ modifiedAt: -1 });
|
||||
CardComments._collection.createIndex({ cardId: 1, createdAt: -1 });
|
||||
});
|
||||
|
||||
CardComments.after.insert((userId, doc) => {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import moment from 'moment/min/moment-with-locales';
|
||||
import {
|
||||
ALLOWED_COLORS,
|
||||
TYPE_CARD,
|
||||
TYPE_LINKED_BOARD,
|
||||
TYPE_LINKED_CARD,
|
||||
} from '../config/const';
|
||||
import Attachments from "./attachments";
|
||||
import Attachments, { fileStoreStrategyFactory } from "./attachments";
|
||||
import { copyFile } from './lib/fileStoreStrategy.js';
|
||||
|
||||
|
||||
Cards = new Mongo.Collection('cards');
|
||||
|
|
@ -585,11 +587,11 @@ Cards.helpers({
|
|||
const _id = Cards.insert(this);
|
||||
|
||||
// Copy attachments
|
||||
oldCard.attachments().forEach(att => {
|
||||
att.cardId = _id;
|
||||
delete att._id;
|
||||
return Attachments.insert(att);
|
||||
});
|
||||
oldCard.attachments()
|
||||
.map(att => att.get())
|
||||
.forEach(att => {
|
||||
copyFile(att, _id, fileStoreStrategyFactory);
|
||||
});
|
||||
|
||||
// copy checklists
|
||||
Checklists.find({ cardId: oldId }).forEach(ch => {
|
||||
|
|
@ -689,6 +691,53 @@ Cards.helpers({
|
|||
return _.contains(this.labelIds, labelId);
|
||||
},
|
||||
|
||||
/** returns the sort number of a list
|
||||
* @param listId a list id
|
||||
* @param swimlaneId a swimlane id
|
||||
* top sorting of the card at the top if true, or from the bottom if false
|
||||
*/
|
||||
getSort(listId, swimlaneId, top) {
|
||||
if (!_.isBoolean(top)) {
|
||||
top = true;
|
||||
}
|
||||
if (!listId) {
|
||||
listId = this.listId;
|
||||
}
|
||||
if (!swimlaneId) {
|
||||
swimlaneId = this.swimlaneId;
|
||||
}
|
||||
const selector = {
|
||||
listId: listId,
|
||||
swimlaneId: swimlaneId,
|
||||
archived: false,
|
||||
};
|
||||
const sorting = top ? 1 : -1;
|
||||
const card = Cards.findOne(selector, { sort: { sort: sorting } });
|
||||
let ret = null
|
||||
if (card) {
|
||||
ret = card.sort;
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
|
||||
/** returns the sort number of a list from the card at the top
|
||||
* @param listId a list id
|
||||
* @param swimlaneId a swimlane id
|
||||
*/
|
||||
getMinSort(listId, swimlaneId) {
|
||||
const ret = this.getSort(listId, swimlaneId, true);
|
||||
return ret;
|
||||
},
|
||||
|
||||
/** returns the sort number of a list from the card at the bottom
|
||||
* @param listId a list id
|
||||
* @param swimlaneId a swimlane id
|
||||
*/
|
||||
getMaxSort(listId, swimlaneId) {
|
||||
const ret = this.getSort(listId, swimlaneId, false);
|
||||
return ret;
|
||||
},
|
||||
|
||||
user() {
|
||||
return Users.findOne(this.userId);
|
||||
},
|
||||
|
|
@ -737,17 +786,15 @@ Cards.helpers({
|
|||
},
|
||||
|
||||
attachments() {
|
||||
let id = this._id;
|
||||
if (this.isLinkedCard()) {
|
||||
return Attachments.find(
|
||||
{ 'meta.cardId': this.linkedId },
|
||||
{ sort: { uploadedAt: -1 } },
|
||||
).each();
|
||||
} else {
|
||||
return Attachments.find(
|
||||
{ 'meta.cardId': this._id },
|
||||
{ sort: { uploadedAt: -1 } },
|
||||
).each();
|
||||
id = this.linkedId;
|
||||
}
|
||||
let ret = Attachments.find(
|
||||
{ 'meta.cardId': id },
|
||||
{ sort: { uploadedAt: -1 } },
|
||||
).each();
|
||||
return ret;
|
||||
},
|
||||
|
||||
cover() {
|
||||
|
|
@ -2992,14 +3039,14 @@ if (Meteor.isServer) {
|
|||
// Cards are often fetched within a board, so we create an index to make these
|
||||
// queries more efficient.
|
||||
Meteor.startup(() => {
|
||||
Cards._collection._ensureIndex({ modifiedAt: -1 });
|
||||
Cards._collection._ensureIndex({ boardId: 1, createdAt: -1 });
|
||||
Cards._collection.createIndex({ modifiedAt: -1 });
|
||||
Cards._collection.createIndex({ boardId: 1, createdAt: -1 });
|
||||
// https://github.com/wekan/wekan/issues/1863
|
||||
// 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).
|
||||
// With a huge database, this result in a very slow app and high CPU on the mongodb side.
|
||||
// To correct it, add Index to parentId:
|
||||
Cards._collection._ensureIndex({ parentId: 1 });
|
||||
Cards._collection.createIndex({ parentId: 1 });
|
||||
// let notifydays = parseInt(process.env.NOTIFY_DUE_DAYS_BEFORE_AND_AFTER) || 2; // default as 2 days b4 and after
|
||||
// let notifyitvl = parseInt(process.env.NOTIFY_DUE_AT_HOUR_OF_DAY) || 3600 * 24 * 1e3; // default interval as one day
|
||||
// Meteor.call("findDueCards",notifydays,notifyitvl);
|
||||
|
|
|
|||
|
|
@ -213,9 +213,9 @@ function publishChekListUncompleted(userId, doc) {
|
|||
// Activities
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(() => {
|
||||
ChecklistItems._collection._ensureIndex({ modifiedAt: -1 });
|
||||
ChecklistItems._collection._ensureIndex({ checklistId: 1 });
|
||||
ChecklistItems._collection._ensureIndex({ cardId: 1 });
|
||||
ChecklistItems._collection.createIndex({ modifiedAt: -1 });
|
||||
ChecklistItems._collection.createIndex({ checklistId: 1 });
|
||||
ChecklistItems._collection.createIndex({ cardId: 1 });
|
||||
});
|
||||
|
||||
ChecklistItems.after.update((userId, doc, fieldNames) => {
|
||||
|
|
|
|||
|
|
@ -195,8 +195,8 @@ Checklists.mutations({
|
|||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(() => {
|
||||
Checklists._collection._ensureIndex({ modifiedAt: -1 });
|
||||
Checklists._collection._ensureIndex({ cardId: 1, createdAt: 1 });
|
||||
Checklists._collection.createIndex({ modifiedAt: -1 });
|
||||
Checklists._collection.createIndex({ cardId: 1, createdAt: 1 });
|
||||
});
|
||||
|
||||
Checklists.after.insert((userId, doc) => {
|
||||
|
|
|
|||
|
|
@ -231,8 +231,8 @@ function customFieldEdit(userId, doc) {
|
|||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(() => {
|
||||
CustomFields._collection._ensureIndex({ modifiedAt: -1 });
|
||||
CustomFields._collection._ensureIndex({ boardIds: 1 });
|
||||
CustomFields._collection.createIndex({ modifiedAt: -1 });
|
||||
CustomFields._collection.createIndex({ boardIds: 1 });
|
||||
});
|
||||
|
||||
CustomFields.after.insert((userId, doc) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
import { Exporter } from './exporter';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
/* global JsonRoutes */
|
||||
if (Meteor.isServer) {
|
||||
import { Picker } from 'meteor/communitypackages:picker';
|
||||
|
||||
// todo XXX once we have a real API in place, move that route there
|
||||
// todo XXX also share the route definition between the client and the server
|
||||
// so that we could use something like
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { TAPi18n } from '/imports/i18n';
|
||||
import { runOnServer } from './runOnServer';
|
||||
|
||||
runOnServer(function() {
|
||||
|
|
@ -5,6 +6,7 @@ runOnServer(function() {
|
|||
// it here we use runOnServer to have it inside a function instead of an
|
||||
// if (Meteor.isServer) block
|
||||
import { ExporterExcel } from './server/ExporterExcel';
|
||||
import { Picker } from 'meteor/communitypackages:picker';
|
||||
|
||||
// todo XXX once we have a real API in place, move that route there
|
||||
// todo XXX also share the route definition between the client and the server
|
||||
|
|
@ -49,12 +51,12 @@ runOnServer(function() {
|
|||
isAdmin: true,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
let userLanguage = 'en';
|
||||
if(user && user.profile){
|
||||
userLanguage = user.profile.language
|
||||
}
|
||||
|
||||
|
||||
const exporterExcel = new ExporterExcel(boardId, userLanguage);
|
||||
if (exporterExcel.canExport(user) || impersonateDone) {
|
||||
if (impersonateDone) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { TAPi18n } from '/imports/i18n';
|
||||
import { runOnServer } from './runOnServer';
|
||||
|
||||
runOnServer(function() {
|
||||
|
|
@ -5,6 +6,7 @@ runOnServer(function() {
|
|||
// it here we use runOnServer to have it inside a function instead of an
|
||||
// if (Meteor.isServer) block
|
||||
import { ExporterCardPDF } from './server/ExporterCardPDF';
|
||||
import { Picker } from 'meteor/communitypackages:picker';
|
||||
|
||||
// todo XXX once we have a real API in place, move that route there
|
||||
// todo XXX also share the route definition between the client and the server
|
||||
|
|
|
|||
|
|
@ -1,4 +1,8 @@
|
|||
import moment from 'moment/min/moment-with-locales';
|
||||
const Papa = require('papaparse');
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
|
||||
//const stringify = require('csv-stringify');
|
||||
|
||||
//const stringify = require('csv-stringify');
|
||||
|
||||
|
|
|
|||
|
|
@ -121,8 +121,8 @@ Integrations.allow({
|
|||
//INTEGRATIONS REST API
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(() => {
|
||||
Integrations._collection._ensureIndex({ modifiedAt: -1 });
|
||||
Integrations._collection._ensureIndex({ boardId: 1 });
|
||||
Integrations._collection.createIndex({ modifiedAt: -1 });
|
||||
Integrations._collection.createIndex({ boardId: 1 });
|
||||
});
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ InvitationCodes.helpers({
|
|||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(() => {
|
||||
InvitationCodes._collection._ensureIndex({ modifiedAt: -1 });
|
||||
InvitationCodes._collection.createIndex({ modifiedAt: -1 });
|
||||
});
|
||||
Boards.deny({
|
||||
fetch: ['members'],
|
||||
|
|
|
|||
|
|
@ -114,6 +114,13 @@ class FileStoreStrategy {
|
|||
unlink() {
|
||||
}
|
||||
|
||||
/** rename the file (physical)
|
||||
* @li at database the filename is updated after this method
|
||||
* @param newFilePath the new file path
|
||||
*/
|
||||
rename(newFilePath) {
|
||||
}
|
||||
|
||||
/** return the storage name
|
||||
* @return the storage name
|
||||
*/
|
||||
|
|
@ -287,6 +294,14 @@ export class FileStoreStrategyFilesystem extends FileStoreStrategy {
|
|||
fs.unlink(filePath, () => {});
|
||||
}
|
||||
|
||||
/** rename the file (physical)
|
||||
* @li at database the filename is updated after this method
|
||||
* @param newFilePath the new file path
|
||||
*/
|
||||
rename(newFilePath) {
|
||||
fs.renameSync(this.fileObj.versions[this.versionName].path, newFilePath);
|
||||
}
|
||||
|
||||
/** return the storage name
|
||||
* @return the storage name
|
||||
*/
|
||||
|
|
@ -312,11 +327,11 @@ export const moveToStorage = function(fileObj, storageDestination, fileStoreStra
|
|||
const writeStream = strategyWrite.getWriteStream(filePath);
|
||||
|
||||
writeStream.on('error', error => {
|
||||
console.error('[writeStream error]: ', error, fileObjId);
|
||||
console.error('[writeStream error]: ', error, fileObj._id);
|
||||
});
|
||||
|
||||
readStream.on('error', error => {
|
||||
console.error('[readStream error]: ', error, fileObjId);
|
||||
console.error('[readStream error]: ', error, fileObj._id);
|
||||
});
|
||||
|
||||
writeStream.on('finish', Meteor.bindEnvironment((finishedData) => {
|
||||
|
|
@ -336,3 +351,69 @@ export const moveToStorage = function(fileObj, storageDestination, fileStoreStra
|
|||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const copyFile = function(fileObj, newCardId, fileStoreStrategyFactory) {
|
||||
const versionName = "original";
|
||||
const strategyRead = fileStoreStrategyFactory.getFileStrategy(fileObj, versionName);
|
||||
const readStream = strategyRead.getReadStream();
|
||||
const strategyWrite = fileStoreStrategyFactory.getFileStrategy(fileObj, versionName, STORAGE_NAME_FILESYSTEM);
|
||||
|
||||
const tempPath = path.join(fileStoreStrategyFactory.storagePath, Random.id() + "-" + versionName + "-" + fileObj.name);
|
||||
const writeStream = strategyWrite.getWriteStream(tempPath);
|
||||
|
||||
writeStream.on('error', error => {
|
||||
console.error('[writeStream error]: ', error, fileObj._id);
|
||||
});
|
||||
|
||||
readStream.on('error', error => {
|
||||
console.error('[readStream error]: ', error, fileObj._id);
|
||||
});
|
||||
|
||||
// https://forums.meteor.com/t/meteor-code-must-always-run-within-a-fiber-try-wrapping-callbacks-that-you-pass-to-non-meteor-libraries-with-meteor-bindenvironmen/40099/8
|
||||
readStream.on('end', Meteor.bindEnvironment(() => {
|
||||
const fileId = Random.id();
|
||||
Attachments.addFile(
|
||||
tempPath,
|
||||
{
|
||||
fileName: fileObj.name,
|
||||
type: fileObj.type,
|
||||
meta: {
|
||||
boardId: fileObj.meta.boardId,
|
||||
cardId: newCardId,
|
||||
listId: fileObj.meta.listId,
|
||||
swimlaneId: fileObj.meta.swimlaneId,
|
||||
source: 'copy',
|
||||
copyFrom: fileObj._id,
|
||||
copyStorage: strategyRead.getStorageName(),
|
||||
},
|
||||
userId: fileObj.userId,
|
||||
size: fileObj.fileSize,
|
||||
fileId,
|
||||
},
|
||||
(err, fileRef) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
} else {
|
||||
// Set the userId again
|
||||
Attachments.update({ _id: fileRef._id }, { $set: { userId: fileObj.userId } });
|
||||
}
|
||||
},
|
||||
true,
|
||||
);
|
||||
}));
|
||||
|
||||
readStream.pipe(writeStream);
|
||||
};
|
||||
|
||||
export const rename = function(fileObj, newName, fileStoreStrategyFactory) {
|
||||
Object.keys(fileObj.versions).forEach(versionName => {
|
||||
const strategy = fileStoreStrategyFactory.getFileStrategy(fileObj, versionName);
|
||||
const newFilePath = strategy.getNewPath(fileStoreStrategyFactory.storagePath, newName);
|
||||
strategy.rename(newFilePath);
|
||||
|
||||
Attachments.update({ _id: fileObj._id }, { $set: {
|
||||
"name": newName,
|
||||
[`versions.${versionName}.path`]: newFilePath,
|
||||
} });
|
||||
});
|
||||
};
|
||||
|
|
|
|||
51
models/lib/fsHooks/createOnAfterUpload.js
Normal file
51
models/lib/fsHooks/createOnAfterUpload.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import { Meteor } from 'meteor/meteor';
|
||||
import fs from 'fs';
|
||||
|
||||
export const createOnAfterUpload = bucket =>
|
||||
function onAfterUpload(file) {
|
||||
const self = this;
|
||||
|
||||
// here you could manipulate your file
|
||||
// and create a new version, for example a scaled 'thumbnail'
|
||||
// ...
|
||||
|
||||
// then we read all versions we have got so far
|
||||
Object.keys(file.versions).forEach(versionName => {
|
||||
const metadata = { ...file.meta, versionName, fileId: file._id };
|
||||
fs.createReadStream(file.versions[versionName].path)
|
||||
|
||||
// this is where we upload the binary to the bucket using bucket.openUploadStream
|
||||
// see http://mongodb.github.io/node-mongodb-native/3.2/api/GridFSBucket.html#openUploadStream
|
||||
.pipe(
|
||||
bucket.openUploadStream(file.name, {
|
||||
contentType: file.type || 'binary/octet-stream',
|
||||
metadata,
|
||||
}),
|
||||
)
|
||||
|
||||
// and we unlink the file from the fs on any error
|
||||
// that occurred during the upload to prevent zombie files
|
||||
.on('error', err => {
|
||||
// console.error("[createOnAfterUpload error]", err);
|
||||
self.unlink(this.collection.findOne(file._id), versionName); // Unlink files from FS
|
||||
})
|
||||
|
||||
// once we are finished, we attach the gridFS Object id on the
|
||||
// FilesCollection document's meta section and finally unlink the
|
||||
// upload file from the filesystem
|
||||
.on(
|
||||
'finish',
|
||||
Meteor.bindEnvironment(ver => {
|
||||
const property = `versions.${versionName}.meta.gridFsFileId`;
|
||||
|
||||
self.collection.update(file._id, {
|
||||
$set: {
|
||||
[property]: ver._id.toHexString(),
|
||||
},
|
||||
});
|
||||
|
||||
self.unlink(this.collection.findOne(file._id), versionName); // Unlink files from FS
|
||||
}),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
|
@ -415,9 +415,9 @@ Lists.hookOptions.after.update = { fetchPrevious: false };
|
|||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(() => {
|
||||
Lists._collection._ensureIndex({ modifiedAt: -1 });
|
||||
Lists._collection._ensureIndex({ boardId: 1 });
|
||||
Lists._collection._ensureIndex({ archivedAt: -1 });
|
||||
Lists._collection.createIndex({ modifiedAt: -1 });
|
||||
Lists._collection.createIndex({ boardId: 1 });
|
||||
Lists._collection.createIndex({ archivedAt: -1 });
|
||||
});
|
||||
|
||||
Lists.after.insert((userId, doc) => {
|
||||
|
|
|
|||
|
|
@ -267,8 +267,8 @@ if (Meteor.isServer) {
|
|||
if (Meteor.isServer) {
|
||||
// Index for Organization name.
|
||||
Meteor.startup(() => {
|
||||
// Org._collection._ensureIndex({ name: -1 });
|
||||
Org._collection._ensureIndex({ orgDisplayName: 1 });
|
||||
// Org._collection.createIndex({ name: -1 });
|
||||
Org._collection.createIndex({ orgDisplayName: 1 });
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -74,8 +74,8 @@ OrgUser.attachSchema(
|
|||
if (Meteor.isServer) {
|
||||
// Index for Organization User.
|
||||
Meteor.startup(() => {
|
||||
OrgUser._collection._ensureIndex({ orgId: -1 });
|
||||
OrgUser._collection._ensureIndex({ orgId: -1, userId: -1 });
|
||||
OrgUser._collection.createIndex({ orgId: -1 });
|
||||
OrgUser._collection.createIndex({ orgId: -1, userId: -1 });
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
if (Meteor.isServer) {
|
||||
Meteor.startup(() => {
|
||||
// Date of 7 days ago
|
||||
let lastWeek = new Date();
|
||||
lastWeek.setDate(lastWeek.getDate() - 7);
|
||||
|
||||
presences.remove({ ttl: { $lte: lastWeek } });
|
||||
|
||||
// Create index for serverId that is queried often
|
||||
presences._collection._ensureIndex({ serverId: -1 });
|
||||
});
|
||||
}
|
||||
|
|
@ -87,7 +87,7 @@ Rules.allow({
|
|||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(() => {
|
||||
Rules._collection._ensureIndex({ modifiedAt: -1 });
|
||||
Rules._collection.createIndex({ modifiedAt: -1 });
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import moment from 'moment/min/moment-with-locales';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import { createWorkbook } from './createWorkbook';
|
||||
|
||||
// exporter maybe is broken since Gridfs introduced, add fs and path
|
||||
|
|
@ -432,7 +434,7 @@ class ExporterExcel {
|
|||
|
||||
//add blank row
|
||||
ws.addRow().values = ['', '', '', '', '', ''];
|
||||
|
||||
|
||||
//add board description
|
||||
ws.addRow().values = [
|
||||
TAPi18n.__('description','',this.userLanguage),
|
||||
|
|
@ -442,7 +444,7 @@ class ExporterExcel {
|
|||
ws.mergeCells('B3:H3');
|
||||
ws.getRow(3).height = 40;
|
||||
// In MS Excel, we can't use the AutoFit feature on a column that contains a cell merged with cells in other columns.
|
||||
// Likewise, we can't use AutoFit on a row that contains a cell merged with cells in other rows.
|
||||
// Likewise, we can't use AutoFit on a row that contains a cell merged with cells in other rows.
|
||||
ws.getRow(3).font = {
|
||||
name: TAPi18n.__('excel-font'),
|
||||
size: 10,
|
||||
|
|
@ -459,7 +461,7 @@ class ExporterExcel {
|
|||
vertical: 'middle',
|
||||
};
|
||||
cellCenter('A3');
|
||||
|
||||
|
||||
//add blank row
|
||||
ws.addRow().values = ['', '', '', '', '', ''];
|
||||
|
||||
|
|
@ -494,7 +496,7 @@ class ExporterExcel {
|
|||
},
|
||||
numFmt: 'yyyy/mm/dd hh:mm:ss',
|
||||
};
|
||||
|
||||
|
||||
cellCenter('A5');
|
||||
cellCenter('B5');
|
||||
cellCenter('C5');
|
||||
|
|
@ -502,7 +504,7 @@ class ExporterExcel {
|
|||
cellCenter('E5');
|
||||
cellLeft('F5');
|
||||
ws.getRow(5).height = 20;
|
||||
|
||||
|
||||
allBorder('A5');
|
||||
allBorder('B5');
|
||||
allBorder('C5');
|
||||
|
|
@ -786,7 +788,7 @@ class ExporterExcel {
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
//add title line
|
||||
ws2.mergeCells('A1:F1');
|
||||
ws2.getCell('A1').value = result.title;
|
||||
|
|
@ -813,7 +815,7 @@ class ExporterExcel {
|
|||
TAPi18n.__('card','',this.userLanguage),
|
||||
TAPi18n.__('owner','',this.userLanguage),
|
||||
TAPi18n.__('createdAt','',this.userLanguage),
|
||||
TAPi18n.__('last-modified-at','',this.userLanguage),
|
||||
TAPi18n.__('last-modified-at','',this.userLanguage),
|
||||
];
|
||||
ws2.getRow(3).height = 20;
|
||||
ws2.getRow(3).font = {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { TAPi18n } from '/imports/i18n';
|
||||
//var nodemailer = require('nodemailer');
|
||||
|
||||
// Sandstorm context is detected using the METEOR_SETTINGS environment variable
|
||||
|
|
@ -153,7 +154,7 @@ Settings.allow({
|
|||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(() => {
|
||||
Settings._collection._ensureIndex({ modifiedAt: -1 });
|
||||
Settings._collection.createIndex({ modifiedAt: -1 });
|
||||
const setting = Settings.findOne({});
|
||||
if (!setting) {
|
||||
const now = new Date();
|
||||
|
|
|
|||
|
|
@ -331,8 +331,8 @@ Swimlanes.hookOptions.after.update = { fetchPrevious: false };
|
|||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(() => {
|
||||
Swimlanes._collection._ensureIndex({ modifiedAt: -1 });
|
||||
Swimlanes._collection._ensureIndex({ boardId: 1 });
|
||||
Swimlanes._collection.createIndex({ modifiedAt: -1 });
|
||||
Swimlanes._collection.createIndex({ boardId: 1 });
|
||||
});
|
||||
|
||||
Swimlanes.after.insert((userId, doc) => {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ TableVisibilityModeSettings.allow({
|
|||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(() => {
|
||||
TableVisibilityModeSettings._collection._ensureIndex({ modifiedAt: -1 });
|
||||
TableVisibilityModeSettings._collection.createIndex({ modifiedAt: -1 });
|
||||
TableVisibilityModeSettings.upsert(
|
||||
{ _id: 'tableVisibilityMode-allowPrivateOnly' },
|
||||
{
|
||||
|
|
|
|||
|
|
@ -264,7 +264,7 @@ if (Meteor.isServer) {
|
|||
if (Meteor.isServer) {
|
||||
// Index for Team name.
|
||||
Meteor.startup(() => {
|
||||
Team._collection._ensureIndex({ teamDisplayName: 1 });
|
||||
Team._collection.createIndex({ teamDisplayName: 1 });
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import Attachments from "./attachments";
|
||||
import moment from 'moment/min/moment-with-locales';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
|
||||
const DateString = Match.Where(function(dateAsString) {
|
||||
check(dateAsString, String);
|
||||
|
|
@ -447,13 +448,11 @@ export class TrelloCreator {
|
|||
});
|
||||
}
|
||||
};
|
||||
// TODO: Add import attachment with Trello API key
|
||||
// like Python code at wekan/trello/ of https://github.com/wekan/wekan
|
||||
//if (att.url) {
|
||||
// Attachment.load(att.url, opts, cb, true);
|
||||
//} else if (att.file) {
|
||||
// Attachment.write(att.file, opts, cb, true);
|
||||
//}
|
||||
if (att.url) {
|
||||
Attachment.load(att.url, opts, cb, true);
|
||||
} else if (att.file) {
|
||||
Attachment.write(att.file, opts, cb, true);
|
||||
}
|
||||
});
|
||||
|
||||
if (links) {
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ Triggers.helpers({
|
|||
|
||||
if (Meteor.isServer) {
|
||||
Meteor.startup(() => {
|
||||
Triggers._collection._ensureIndex({ modifiedAt: -1 });
|
||||
Triggers._collection.createIndex({ modifiedAt: -1 });
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@ if (Meteor.isServer) {
|
|||
return userId === doc.userId && fieldNames.indexOf('userId') === -1;
|
||||
}
|
||||
Meteor.startup(() => {
|
||||
UnsavedEditCollection._collection._ensureIndex({ modifiedAt: -1 });
|
||||
UnsavedEditCollection._collection._ensureIndex({ userId: 1 });
|
||||
UnsavedEditCollection._collection.createIndex({ modifiedAt: -1 });
|
||||
UnsavedEditCollection._collection.createIndex({ userId: 1 });
|
||||
});
|
||||
UnsavedEditCollection.allow({
|
||||
insert: isAuthor,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
//var nodemailer = require('nodemailer');
|
||||
import { SyncedCron } from 'meteor/percolate:synced-cron';
|
||||
import { TAPi18n } from '/imports/i18n';
|
||||
import ImpersonatedUsers from './impersonatedUsers';
|
||||
|
||||
// Sandstorm context is detected using the METEOR_SETTINGS environment variable
|
||||
|
|
@ -1660,12 +1661,13 @@ if (Meteor.isServer) {
|
|||
// Let mongoDB ensure username unicity
|
||||
Meteor.startup(() => {
|
||||
allowedSortValues.forEach((value) => {
|
||||
Lists._collection._ensureIndex(value);
|
||||
Lists._collection.createIndex(value);
|
||||
});
|
||||
Users._collection._ensureIndex({
|
||||
Users._collection.createIndex({
|
||||
modifiedAt: -1,
|
||||
});
|
||||
Users._collection._ensureIndex(
|
||||
/* Commented out extra index because of IndexOptionsConflict.
|
||||
Users._collection.createIndex(
|
||||
{
|
||||
username: 1,
|
||||
},
|
||||
|
|
@ -1673,6 +1675,7 @@ if (Meteor.isServer) {
|
|||
unique: true,
|
||||
},
|
||||
);
|
||||
*/
|
||||
Meteor.defer(() => {
|
||||
addCronJob();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import moment from 'moment/min/moment-with-locales';
|
||||
|
||||
const DateString = Match.Where(function(dateAsString) {
|
||||
check(dateAsString, String);
|
||||
return moment(dateAsString, moment.ISO_8601).isValid();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue