From 05c53ca01d71a01a608c9ae345475abd67c9939b Mon Sep 17 00:00:00 2001 From: Romulus Urakagi Tsai Date: Mon, 18 Nov 2019 01:47:26 +0000 Subject: Trying to upload an attachment with Meteor-Files --- server/publications/boards.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'server') diff --git a/server/publications/boards.js b/server/publications/boards.js index e3095833..79e578b8 100644 --- a/server/publications/boards.js +++ b/server/publications/boards.js @@ -128,7 +128,7 @@ Meteor.publishRelations('board', function(boardId, isArchived) { // Gather queries and send in bulk const cardComments = this.join(CardComments); cardComments.selector = _ids => ({ cardId: _ids }); - const attachments = this.join(Attachments); + const attachments = this.join(Attachments.collection); attachments.selector = _ids => ({ cardId: _ids }); const checklists = this.join(Checklists); checklists.selector = _ids => ({ cardId: _ids }); -- cgit v1.2.3-1-g7c22 From 4dcdec0084414e7dde9e630add01ecd2865bd941 Mon Sep 17 00:00:00 2001 From: Romulus Urakagi Tsai Date: Wed, 20 Nov 2019 10:40:09 +0000 Subject: Attachment upload from card done, need to fix download link --- server/migrations.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'server') diff --git a/server/migrations.js b/server/migrations.js index 836220f3..7d5a5cca 100644 --- a/server/migrations.js +++ b/server/migrations.js @@ -80,7 +80,7 @@ Migrations.add('lowercase-board-permission', () => { Migrations.add('change-attachments-type-for-non-images', () => { const newTypeForNonImage = 'application/octet-stream'; Attachments.find().forEach(file => { - if (!file.isImage()) { + if (!file.isImage) { Attachments.update( file._id, { @@ -97,7 +97,7 @@ Migrations.add('change-attachments-type-for-non-images', () => { Migrations.add('card-covers', () => { Cards.find().forEach(card => { - const cover = Attachments.findOne({ cardId: card._id, cover: true }); + const cover = Attachments.findOne({ 'meta.cardId': card._id, cover: true }); if (cover) { Cards.update(card._id, { $set: { coverId: cover._id } }, noValidate); } -- cgit v1.2.3-1-g7c22 From b34ed58289a3dae5838d3b621260938a3ecf52d5 Mon Sep 17 00:00:00 2001 From: Romulus Urakagi Tsai Date: Thu, 13 Feb 2020 08:47:41 +0000 Subject: Start writing migration --- server/migrate-attachments.js | 263 ++++++++++++++++++++++++++++++++++++++++++ server/migrations.js | 6 + 2 files changed, 269 insertions(+) create mode 100644 server/migrate-attachments.js (limited to 'server') diff --git a/server/migrate-attachments.js b/server/migrate-attachments.js new file mode 100644 index 00000000..7dcc4d39 --- /dev/null +++ b/server/migrate-attachments.js @@ -0,0 +1,263 @@ +const localFSStore = process.env.ATTACHMENTS_STORE_PATH; +const storeName = 'attachments'; +const defaultStoreOptions = { + beforeWrite: fileObj => { + if (!fileObj.isImage()) { + return { + type: 'application/octet-stream', + }; + } + return {}; + }, +}; +let store; +if (localFSStore) { + // have to reinvent methods from FS.Store.GridFS and FS.Store.FileSystem + const fs = Npm.require('fs'); + const path = Npm.require('path'); + const mongodb = Npm.require('mongodb'); + const Grid = Npm.require('gridfs-stream'); + // calulate the absolute path here, because FS.Store.FileSystem didn't expose the aboslutepath or FS.Store didn't expose api calls :( + let pathname = localFSStore; + /*eslint camelcase: ["error", {allow: ["__meteor_bootstrap__"]}] */ + + if (!pathname && __meteor_bootstrap__ && __meteor_bootstrap__.serverDir) { + pathname = path.join( + __meteor_bootstrap__.serverDir, + `../../../cfs/files/${storeName}`, + ); + } + + if (!pathname) + throw new Error('FS.Store.FileSystem unable to determine path'); + + // Check if we have '~/foo/bar' + if (pathname.split(path.sep)[0] === '~') { + const homepath = + process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; + if (homepath) { + pathname = pathname.replace('~', homepath); + } else { + throw new Error('FS.Store.FileSystem unable to resolve "~" in path'); + } + } + + // Set absolute path + const absolutePath = path.resolve(pathname); + + const _FStore = new FS.Store.FileSystem(storeName, { + path: localFSStore, + ...defaultStoreOptions, + }); + const GStore = { + fileKey(fileObj) { + const key = { + _id: null, + filename: null, + }; + + // If we're passed a fileObj, we retrieve the _id and filename from it. + if (fileObj) { + const info = fileObj._getInfo(storeName, { + updateFileRecordFirst: false, + }); + key._id = info.key || null; + key.filename = + info.name || + fileObj.name({ updateFileRecordFirst: false }) || + `${fileObj.collectionName}-${fileObj._id}`; + } + + // If key._id is null at this point, createWriteStream will let GridFS generate a new ID + return key; + }, + db: undefined, + mongoOptions: { useNewUrlParser: true }, + mongoUrl: process.env.MONGO_URL, + init() { + this._init(err => { + this.inited = !err; + }); + }, + _init(callback) { + const self = this; + mongodb.MongoClient.connect(self.mongoUrl, self.mongoOptions, function( + err, + db, + ) { + if (err) { + return callback(err); + } + self.db = db; + return callback(null); + }); + return; + }, + createReadStream(fileKey, options) { + const self = this; + if (!self.inited) { + self.init(); + return undefined; + } + options = options || {}; + + // Init GridFS + const gfs = new Grid(self.db, mongodb); + + // Set the default streamning settings + const settings = { + _id: new mongodb.ObjectID(fileKey._id), + root: `cfs_gridfs.${storeName}`, + }; + + // Check if this should be a partial read + if ( + typeof options.start !== 'undefined' && + typeof options.end !== 'undefined' + ) { + // Add partial info + settings.range = { + startPos: options.start, + endPos: options.end, + }; + } + return gfs.createReadStream(settings); + }, + }; + GStore.init(); + const CRS = 'createReadStream'; + const _CRS = `_${CRS}`; + const FStore = _FStore._transform; + FStore[_CRS] = FStore[CRS].bind(FStore); + FStore[CRS] = function(fileObj, options) { + let stream; + try { + const localFile = path.join( + absolutePath, + FStore.storage.fileKey(fileObj), + ); + const state = fs.statSync(localFile); + if (state) { + stream = FStore[_CRS](fileObj, options); + } + } catch (e) { + // file is not there, try GridFS ? + stream = undefined; + } + if (stream) return stream; + else { + try { + const stream = GStore[CRS](GStore.fileKey(fileObj), options); + return stream; + } catch (e) { + return undefined; + } + } + }.bind(FStore); + store = _FStore; +} else { + store = new FS.Store.GridFS(localFSStore ? `G${storeName}` : storeName, { + // XXX Add a new store for cover thumbnails so we don't load big images in + // the general board view + // If the uploaded document is not an image we need to enforce browser + // download instead of execution. This is particularly important for HTML + // files that the browser will just execute if we don't serve them with the + // appropriate `application/octet-stream` MIME header which can lead to user + // data leaks. I imagine other formats (like PDF) can also be attack vectors. + // See https://github.com/wekan/wekan/issues/99 + // XXX Should we use `beforeWrite` option of CollectionFS instead of + // collection-hooks? + // We should use `beforeWrite`. + ...defaultStoreOptions, + }); +} +CFSAttachments = new FS.Collection('attachments', { + stores: [store], +}); + +if (Meteor.isServer) { + Meteor.startup(() => { + CFSAttachments.files._ensureIndex({ cardId: 1 }); + }); + + CFSAttachments.allow({ + insert(userId, doc) { + return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); + }, + update(userId, doc) { + return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); + }, + remove(userId, doc) { + return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); + }, + // We authorize the attachment download either: + // - if the board is public, everyone (even unconnected) can download it + // - if the board is private, only board members can download it + download(userId, doc) { + const board = Boards.findOne(doc.boardId); + if (board.isPublic()) { + return true; + } else { + return board.hasMember(userId); + } + }, + + fetch: ['boardId'], + }); +} + +// XXX Enforce a schema for the Attachments CollectionFS + +if (Meteor.isServer) { + CFSAttachments.files.after.insert((userId, doc) => { + // If the attachment doesn't have a source field + // or its source is different than import + if (!doc.source || doc.source !== 'import') { + // Add activity about adding the attachment + Activities.insert({ + userId, + type: 'card', + activityType: 'addAttachment', + attachmentId: doc._id, + boardId: doc.boardId, + cardId: doc.cardId, + listId: doc.listId, + swimlaneId: doc.swimlaneId, + }); + } else { + // Don't add activity about adding the attachment as the activity + // be imported and delete source field + CFSAttachments.update( + { + _id: doc._id, + }, + { + $unset: { + source: '', + }, + }, + ); + } + }); + + CFSAttachments.files.before.remove((userId, doc) => { + Activities.insert({ + userId, + type: 'card', + activityType: 'deleteAttachment', + attachmentId: doc._id, + boardId: doc.boardId, + cardId: doc.cardId, + listId: doc.listId, + swimlaneId: doc.swimlaneId, + }); + }); + + CFSAttachments.files.after.remove((userId, doc) => { + Activities.remove({ + attachmentId: doc._id, + }); + }); +} + +export default CFSAttachments; diff --git a/server/migrations.js b/server/migrations.js index 7d5a5cca..e7c18e09 100644 --- a/server/migrations.js +++ b/server/migrations.js @@ -17,6 +17,7 @@ import Swimlanes from '../models/swimlanes'; import Triggers from '../models/triggers'; import UnsavedEdits from '../models/unsavedEdits'; import Users from '../models/users'; +import CFSAttachments from './migrate-attachments'; // 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 @@ -777,3 +778,8 @@ Migrations.add('fix-incorrect-dates', () => { }), ); }); + +Migrations.add('fix-incorrect-dates', () => { + cas = CFSAttachments.find(); + console.log('cas', cas); +}); -- cgit v1.2.3-1-g7c22 From 5899b9366c23f31149e9de8ed006a7e30b4830d5 Mon Sep 17 00:00:00 2001 From: Romulus Urakagi Tsai Date: Mon, 2 Mar 2020 02:10:54 +0000 Subject: Change coagmano:stylus package to 1.1.0 since 2.0.0 building is super slow --- server/migrations.js | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'server') diff --git a/server/migrations.js b/server/migrations.js index d7c4c724..3861f227 100644 --- a/server/migrations.js +++ b/server/migrations.js @@ -1038,3 +1038,10 @@ Migrations.add('fix-incorrect-dates', () => { cas = CFSAttachments.find(); console.log('cas', cas); }); + +Migrations.add('change-attachment-library', () => { + console.log('migration called here'); + Migrations.rollback('change-attachment-library'); + console.log('migration rollbacked'); +}); + -- cgit v1.2.3-1-g7c22 From 0a1bfd37b3b1b2f4108a734c0449c1bd5a1b691f Mon Sep 17 00:00:00 2001 From: Romulus Urakagi Tsai Date: Tue, 5 May 2020 14:08:36 +0800 Subject: Migrating attachments --- server/migrations.js | 38 ++++++- server/old-attachments-migration.js | 212 ++++++++++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+), 3 deletions(-) create mode 100644 server/old-attachments-migration.js (limited to 'server') diff --git a/server/migrations.js b/server/migrations.js index 3861f227..33f061f5 100644 --- a/server/migrations.js +++ b/server/migrations.js @@ -1039,9 +1039,41 @@ Migrations.add('fix-incorrect-dates', () => { console.log('cas', cas); }); +import { MongoInternals } from 'meteor/mongo'; + Migrations.add('change-attachment-library', () => { - console.log('migration called here'); - Migrations.rollback('change-attachment-library'); - console.log('migration rollbacked'); + const http = require('http'); + const fs = require('fs'); + CFSAttachments.find().forEach(file => { + const bucket = new MongoInternals.NpmModule.GridFSBucket(MongoInternals.defaultRemoteCollectionDriver().mongo.db, {bucketName: 'cfs_gridfs.attachments'}); + const gfsId = new MongoInternals.NpmModule.ObjectID(file.copies.attachments.key); + const reader = bucket.openDownloadStream(gfsId); + const path = `/var/attachments/${file.name()}`; + const fd = fs.createWriteStream(path); + reader.pipe(fd); + let opts = { + fileName: file.name(), + type: file.type(), + fileId: file._id, + meta: { + userId: file.userId, + boardId: file.boardId, + cardId: file.cardId + } + }; + if (file.listId) { + opts.meta.listId = file.listId; + } + if (file.swimlaneId) { + opts.meta.swimlaneId = file.swimlaneId; + } + Attachments.addFile(path, opts, (err, fileRef) => { + if (err) { + console.log('error when migrating ', fileName, err); + } else { + file.remove(); + } + }); + }); }); diff --git a/server/old-attachments-migration.js b/server/old-attachments-migration.js new file mode 100644 index 00000000..3a6aa85d --- /dev/null +++ b/server/old-attachments-migration.js @@ -0,0 +1,212 @@ +const localFSStore = process.env.ATTACHMENTS_STORE_PATH; +const storeName = 'attachments'; +const defaultStoreOptions = { + beforeWrite: fileObj => { + if (!fileObj.isImage()) { + return { + type: 'application/octet-stream', + }; + } + return {}; + }, +}; +let store; +if (localFSStore) { + // have to reinvent methods from FS.Store.GridFS and FS.Store.FileSystem + const fs = Npm.require('fs'); + const path = Npm.require('path'); + const mongodb = Npm.require('mongodb'); + const Grid = Npm.require('gridfs-stream'); + // calulate the absolute path here, because FS.Store.FileSystem didn't expose the aboslutepath or FS.Store didn't expose api calls :( + let pathname = localFSStore; + /*eslint camelcase: ["error", {allow: ["__meteor_bootstrap__"]}] */ + + if (!pathname && __meteor_bootstrap__ && __meteor_bootstrap__.serverDir) { + pathname = path.join( + __meteor_bootstrap__.serverDir, + `../../../cfs/files/${storeName}`, + ); + } + + if (!pathname) + throw new Error('FS.Store.FileSystem unable to determine path'); + + // Check if we have '~/foo/bar' + if (pathname.split(path.sep)[0] === '~') { + const homepath = + process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; + if (homepath) { + pathname = pathname.replace('~', homepath); + } else { + throw new Error('FS.Store.FileSystem unable to resolve "~" in path'); + } + } + + // Set absolute path + const absolutePath = path.resolve(pathname); + + const _FStore = new FS.Store.FileSystem(storeName, { + path: localFSStore, + ...defaultStoreOptions, + }); + const GStore = { + fileKey(fileObj) { + const key = { + _id: null, + filename: null, + }; + + // If we're passed a fileObj, we retrieve the _id and filename from it. + if (fileObj) { + const info = fileObj._getInfo(storeName, { + updateFileRecordFirst: false, + }); + key._id = info.key || null; + key.filename = + info.name || + fileObj.name({ updateFileRecordFirst: false }) || + `${fileObj.collectionName}-${fileObj._id}`; + } + + // If key._id is null at this point, createWriteStream will let GridFS generate a new ID + return key; + }, + db: undefined, + mongoOptions: { useNewUrlParser: true }, + mongoUrl: process.env.MONGO_URL, + init() { + this._init(err => { + this.inited = !err; + }); + }, + _init(callback) { + const self = this; + mongodb.MongoClient.connect(self.mongoUrl, self.mongoOptions, function( + err, + db, + ) { + if (err) { + return callback(err); + } + self.db = db; + return callback(null); + }); + return; + }, + createReadStream(fileKey, options) { + const self = this; + if (!self.inited) { + self.init(); + return undefined; + } + options = options || {}; + + // Init GridFS + const gfs = new Grid(self.db, mongodb); + + // Set the default streamning settings + const settings = { + _id: new mongodb.ObjectID(fileKey._id), + root: `cfs_gridfs.${storeName}`, + }; + + // Check if this should be a partial read + if ( + typeof options.start !== 'undefined' && + typeof options.end !== 'undefined' + ) { + // Add partial info + settings.range = { + startPos: options.start, + endPos: options.end, + }; + } + return gfs.createReadStream(settings); + }, + }; + GStore.init(); + const CRS = 'createReadStream'; + const _CRS = `_${CRS}`; + const FStore = _FStore._transform; + FStore[_CRS] = FStore[CRS].bind(FStore); + FStore[CRS] = function(fileObj, options) { + let stream; + try { + const localFile = path.join( + absolutePath, + FStore.storage.fileKey(fileObj), + ); + const state = fs.statSync(localFile); + if (state) { + stream = FStore[_CRS](fileObj, options); + } + } catch (e) { + // file is not there, try GridFS ? + stream = undefined; + } + if (stream) return stream; + else { + try { + const stream = GStore[CRS](GStore.fileKey(fileObj), options); + return stream; + } catch (e) { + return undefined; + } + } + }.bind(FStore); + store = _FStore; +} else { + store = new FS.Store.GridFS(localFSStore ? `G${storeName}` : storeName, { + // XXX Add a new store for cover thumbnails so we don't load big images in + // the general board view + // If the uploaded document is not an image we need to enforce browser + // download instead of execution. This is particularly important for HTML + // files that the browser will just execute if we don't serve them with the + // appropriate `application/octet-stream` MIME header which can lead to user + // data leaks. I imagine other formats (like PDF) can also be attack vectors. + // See https://github.com/wekan/wekan/issues/99 + // XXX Should we use `beforeWrite` option of CollectionFS instead of + // collection-hooks? + // We should use `beforeWrite`. + ...defaultStoreOptions, + }); +} +CFSAttachments = new FS.Collection('attachments', { + stores: [store], +}); + +if (Meteor.isServer) { + Meteor.startup(() => { + CFSAttachments.files._ensureIndex({ cardId: 1 }); + }); + + CFSAttachments.allow({ + insert(userId, doc) { + return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); + }, + update(userId, doc) { + return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); + }, + remove(userId, doc) { + return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); + }, + // We authorize the attachment download either: + // - if the board is public, everyone (even unconnected) can download it + // - if the board is private, only board members can download it + download(userId, doc) { + if (Meteor.isServer) { + return true; + } + const board = Boards.findOne(doc.boardId); + if (board.isPublic()) { + return true; + } else { + return board.hasMember(userId); + } + }, + + fetch: ['boardId'], + }); +} + +export default CFSAttachments; -- cgit v1.2.3-1-g7c22 From 269698ba780a0af5fe0b24fc3c2c43d1f3f1b49d Mon Sep 17 00:00:00 2001 From: Romulus Urakagi Tsai Date: Tue, 5 May 2020 14:18:10 +0800 Subject: Attachments download --- server/migrate-attachments.js | 263 ------------------------------------------ 1 file changed, 263 deletions(-) delete mode 100644 server/migrate-attachments.js (limited to 'server') diff --git a/server/migrate-attachments.js b/server/migrate-attachments.js deleted file mode 100644 index 7dcc4d39..00000000 --- a/server/migrate-attachments.js +++ /dev/null @@ -1,263 +0,0 @@ -const localFSStore = process.env.ATTACHMENTS_STORE_PATH; -const storeName = 'attachments'; -const defaultStoreOptions = { - beforeWrite: fileObj => { - if (!fileObj.isImage()) { - return { - type: 'application/octet-stream', - }; - } - return {}; - }, -}; -let store; -if (localFSStore) { - // have to reinvent methods from FS.Store.GridFS and FS.Store.FileSystem - const fs = Npm.require('fs'); - const path = Npm.require('path'); - const mongodb = Npm.require('mongodb'); - const Grid = Npm.require('gridfs-stream'); - // calulate the absolute path here, because FS.Store.FileSystem didn't expose the aboslutepath or FS.Store didn't expose api calls :( - let pathname = localFSStore; - /*eslint camelcase: ["error", {allow: ["__meteor_bootstrap__"]}] */ - - if (!pathname && __meteor_bootstrap__ && __meteor_bootstrap__.serverDir) { - pathname = path.join( - __meteor_bootstrap__.serverDir, - `../../../cfs/files/${storeName}`, - ); - } - - if (!pathname) - throw new Error('FS.Store.FileSystem unable to determine path'); - - // Check if we have '~/foo/bar' - if (pathname.split(path.sep)[0] === '~') { - const homepath = - process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; - if (homepath) { - pathname = pathname.replace('~', homepath); - } else { - throw new Error('FS.Store.FileSystem unable to resolve "~" in path'); - } - } - - // Set absolute path - const absolutePath = path.resolve(pathname); - - const _FStore = new FS.Store.FileSystem(storeName, { - path: localFSStore, - ...defaultStoreOptions, - }); - const GStore = { - fileKey(fileObj) { - const key = { - _id: null, - filename: null, - }; - - // If we're passed a fileObj, we retrieve the _id and filename from it. - if (fileObj) { - const info = fileObj._getInfo(storeName, { - updateFileRecordFirst: false, - }); - key._id = info.key || null; - key.filename = - info.name || - fileObj.name({ updateFileRecordFirst: false }) || - `${fileObj.collectionName}-${fileObj._id}`; - } - - // If key._id is null at this point, createWriteStream will let GridFS generate a new ID - return key; - }, - db: undefined, - mongoOptions: { useNewUrlParser: true }, - mongoUrl: process.env.MONGO_URL, - init() { - this._init(err => { - this.inited = !err; - }); - }, - _init(callback) { - const self = this; - mongodb.MongoClient.connect(self.mongoUrl, self.mongoOptions, function( - err, - db, - ) { - if (err) { - return callback(err); - } - self.db = db; - return callback(null); - }); - return; - }, - createReadStream(fileKey, options) { - const self = this; - if (!self.inited) { - self.init(); - return undefined; - } - options = options || {}; - - // Init GridFS - const gfs = new Grid(self.db, mongodb); - - // Set the default streamning settings - const settings = { - _id: new mongodb.ObjectID(fileKey._id), - root: `cfs_gridfs.${storeName}`, - }; - - // Check if this should be a partial read - if ( - typeof options.start !== 'undefined' && - typeof options.end !== 'undefined' - ) { - // Add partial info - settings.range = { - startPos: options.start, - endPos: options.end, - }; - } - return gfs.createReadStream(settings); - }, - }; - GStore.init(); - const CRS = 'createReadStream'; - const _CRS = `_${CRS}`; - const FStore = _FStore._transform; - FStore[_CRS] = FStore[CRS].bind(FStore); - FStore[CRS] = function(fileObj, options) { - let stream; - try { - const localFile = path.join( - absolutePath, - FStore.storage.fileKey(fileObj), - ); - const state = fs.statSync(localFile); - if (state) { - stream = FStore[_CRS](fileObj, options); - } - } catch (e) { - // file is not there, try GridFS ? - stream = undefined; - } - if (stream) return stream; - else { - try { - const stream = GStore[CRS](GStore.fileKey(fileObj), options); - return stream; - } catch (e) { - return undefined; - } - } - }.bind(FStore); - store = _FStore; -} else { - store = new FS.Store.GridFS(localFSStore ? `G${storeName}` : storeName, { - // XXX Add a new store for cover thumbnails so we don't load big images in - // the general board view - // If the uploaded document is not an image we need to enforce browser - // download instead of execution. This is particularly important for HTML - // files that the browser will just execute if we don't serve them with the - // appropriate `application/octet-stream` MIME header which can lead to user - // data leaks. I imagine other formats (like PDF) can also be attack vectors. - // See https://github.com/wekan/wekan/issues/99 - // XXX Should we use `beforeWrite` option of CollectionFS instead of - // collection-hooks? - // We should use `beforeWrite`. - ...defaultStoreOptions, - }); -} -CFSAttachments = new FS.Collection('attachments', { - stores: [store], -}); - -if (Meteor.isServer) { - Meteor.startup(() => { - CFSAttachments.files._ensureIndex({ cardId: 1 }); - }); - - CFSAttachments.allow({ - insert(userId, doc) { - return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); - }, - update(userId, doc) { - return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); - }, - remove(userId, doc) { - return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); - }, - // We authorize the attachment download either: - // - if the board is public, everyone (even unconnected) can download it - // - if the board is private, only board members can download it - download(userId, doc) { - const board = Boards.findOne(doc.boardId); - if (board.isPublic()) { - return true; - } else { - return board.hasMember(userId); - } - }, - - fetch: ['boardId'], - }); -} - -// XXX Enforce a schema for the Attachments CollectionFS - -if (Meteor.isServer) { - CFSAttachments.files.after.insert((userId, doc) => { - // If the attachment doesn't have a source field - // or its source is different than import - if (!doc.source || doc.source !== 'import') { - // Add activity about adding the attachment - Activities.insert({ - userId, - type: 'card', - activityType: 'addAttachment', - attachmentId: doc._id, - boardId: doc.boardId, - cardId: doc.cardId, - listId: doc.listId, - swimlaneId: doc.swimlaneId, - }); - } else { - // Don't add activity about adding the attachment as the activity - // be imported and delete source field - CFSAttachments.update( - { - _id: doc._id, - }, - { - $unset: { - source: '', - }, - }, - ); - } - }); - - CFSAttachments.files.before.remove((userId, doc) => { - Activities.insert({ - userId, - type: 'card', - activityType: 'deleteAttachment', - attachmentId: doc._id, - boardId: doc.boardId, - cardId: doc.cardId, - listId: doc.listId, - swimlaneId: doc.swimlaneId, - }); - }); - - CFSAttachments.files.after.remove((userId, doc) => { - Activities.remove({ - attachmentId: doc._id, - }); - }); -} - -export default CFSAttachments; -- cgit v1.2.3-1-g7c22 From 7dc0bbd7b26ef50ebd6c0a4d719658c2d288c2d3 Mon Sep 17 00:00:00 2001 From: Romulus Urakagi Tsai Date: Wed, 6 May 2020 11:15:01 +0800 Subject: Set correct storage location --- server/migrations.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'server') diff --git a/server/migrations.js b/server/migrations.js index 33f061f5..b02ca246 100644 --- a/server/migrations.js +++ b/server/migrations.js @@ -1048,7 +1048,11 @@ Migrations.add('change-attachment-library', () => { const bucket = new MongoInternals.NpmModule.GridFSBucket(MongoInternals.defaultRemoteCollectionDriver().mongo.db, {bucketName: 'cfs_gridfs.attachments'}); const gfsId = new MongoInternals.NpmModule.ObjectID(file.copies.attachments.key); const reader = bucket.openDownloadStream(gfsId); - const path = `/var/attachments/${file.name()}`; + let store = Attachments.storagePath(); + if (store.charAt(store.length - 1) === '/') { + store = store.substring(0, store.length - 1); + } + const path = `${store}/${file.name()}`; const fd = fs.createWriteStream(path); reader.pipe(fd); let opts = { -- cgit v1.2.3-1-g7c22 From 4c5a2fbd1f8ad2f2447235442bf96b893f18a409 Mon Sep 17 00:00:00 2001 From: Romulus Urakagi Tsai Date: Thu, 14 May 2020 14:55:54 +0800 Subject: Card clone OK --- server/migrations.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'server') diff --git a/server/migrations.js b/server/migrations.js index e330f043..3577c78d 100644 --- a/server/migrations.js +++ b/server/migrations.js @@ -1061,6 +1061,7 @@ Migrations.add('change-attachment-library', () => { let opts = { fileName: file.name(), type: file.type(), + size: file.size(), fileId: file._id, meta: { userId: file.userId, @@ -1077,8 +1078,6 @@ Migrations.add('change-attachment-library', () => { Attachments.addFile(path, opts, (err, fileRef) => { if (err) { console.log('error when migrating ', fileName, err); - } else { - file.remove(); } }); }); -- cgit v1.2.3-1-g7c22 From 09ce3e464fd609b3ecc8bec5263ab06093c3a442 Mon Sep 17 00:00:00 2001 From: Romulus Urakagi Tsai Date: Thu, 14 May 2020 16:43:59 +0800 Subject: Fix typo --- server/migrations.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'server') diff --git a/server/migrations.js b/server/migrations.js index 3577c78d..840ab170 100644 --- a/server/migrations.js +++ b/server/migrations.js @@ -1077,7 +1077,7 @@ Migrations.add('change-attachment-library', () => { } Attachments.addFile(path, opts, (err, fileRef) => { if (err) { - console.log('error when migrating ', fileName, err); + console.log('error when migrating ', fileRef.name, err); } }); }); -- cgit v1.2.3-1-g7c22 From b80396f627665119cd38f11f2d466ce53ec573ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E4=BB=B2=E6=98=8E=20=28Romulus=20Urakagi=20Tsai?= =?UTF-8?q?=29?= Date: Thu, 14 May 2020 17:36:57 +0800 Subject: Purge unneeded require --- server/migrations.js | 1 - 1 file changed, 1 deletion(-) (limited to 'server') diff --git a/server/migrations.js b/server/migrations.js index 840ab170..bf019312 100644 --- a/server/migrations.js +++ b/server/migrations.js @@ -1045,7 +1045,6 @@ Migrations.add('add-sort-field-to-boards', () => { import { MongoInternals } from 'meteor/mongo'; Migrations.add('change-attachment-library', () => { - const http = require('http'); const fs = require('fs'); CFSAttachments.find().forEach(file => { const bucket = new MongoInternals.NpmModule.GridFSBucket(MongoInternals.defaultRemoteCollectionDriver().mongo.db, {bucketName: 'cfs_gridfs.attachments'}); -- cgit v1.2.3-1-g7c22 From 4064f3f4063136c97aa7bcbcdc18fec923934b74 Mon Sep 17 00:00:00 2001 From: Romulus Urakagi Tsai Date: Wed, 20 May 2020 15:11:22 +0800 Subject: Fix migrated attachment not readable bug Remove reduandant files --- server/migrations.js | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) (limited to 'server') diff --git a/server/migrations.js b/server/migrations.js index 840ab170..887a60e4 100644 --- a/server/migrations.js +++ b/server/migrations.js @@ -80,7 +80,7 @@ Migrations.add('lowercase-board-permission', () => { Migrations.add('change-attachments-type-for-non-images', () => { const newTypeForNonImage = 'application/octet-stream'; Attachments.find().forEach(file => { - if (!file.isImage()) { + if (!file.isImage) { Attachments.update( file._id, { @@ -1058,27 +1058,29 @@ Migrations.add('change-attachment-library', () => { const path = `${store}/${file.name()}`; const fd = fs.createWriteStream(path); reader.pipe(fd); - let opts = { - fileName: file.name(), - type: file.type(), - size: file.size(), - fileId: file._id, - meta: { - userId: file.userId, - boardId: file.boardId, - cardId: file.cardId - } - }; - if (file.listId) { - opts.meta.listId = file.listId; - } - if (file.swimlaneId) { - opts.meta.swimlaneId = file.swimlaneId; - } - Attachments.addFile(path, opts, (err, fileRef) => { - if (err) { - console.log('error when migrating ', fileRef.name, err); + reader.on('end', () => { + let opts = { + fileName: file.name(), + type: file.type(), + size: file.size(), + fileId: file._id, + meta: { + userId: file.userId, + boardId: file.boardId, + cardId: file.cardId + } + }; + if (file.listId) { + opts.meta.listId = file.listId; } + if (file.swimlaneId) { + opts.meta.swimlaneId = file.swimlaneId; + } + Attachments.addFile(path, opts, (err, fileRef) => { + if (err) { + console.log('error when migrating', file.name(), err); + } + }); }); }); }); -- cgit v1.2.3-1-g7c22