-
-
Notifications
You must be signed in to change notification settings - Fork 167
GridFS Integration
dr.dimitru edited this page Dec 13, 2016
·
8 revisions
Example below shows how to handle (store, serve, remove) uploaded files via GridFS.
Firstly you need to install gridfs-stream:
npm install --save gridfs-stream
Or:
meteor npm install --save gridfs-stream
Create a FilesCollection
instance:
import { Meteor } from 'meteor/meteor';
import { FilesCollection } from 'meteor/ostrio:files';
export const Images = new FilesCollection({
debug: false, // Change to `true` for debugging
collectionName: 'images',
allowClientCode: false,
onBeforeUpload(file) {
if (file.size <= 10485760 && /png|jpg|jpeg/i.test(file.extension)) return true;
return 'Please upload image, with size equal or less than 10MB';
},
});
if (Meteor.isServer) {
Images.denyClient();
}
Import and set up required variables:
import Grid from 'gridfs-stream'; // We'll use this package to work with GridFS
import fs from 'fs'; // Required to read files initially uploaded via Meteor-Files
import { MongoInternals } from 'meteor/mongo';
// Set up gfs instance
let gfs;
if (Meteor.isServer) {
gfs = Grid(
MongoInternals.defaultRemoteCollectionDriver().mongo.db,
MongoInternals.NpmModule
);
}
Add onAfterUpload
and interceptDownload
hooks that would move file to GridFS once it's uploaded, and serve file from GridFS on request:
onAfterUpload(image) {
// Move file to GridFS
Object.keys(image.versions).forEach(versionName => {
const metadata = { versionName, imageId: image._id, storedAt: new Date() }; // Optional
const writeStream = gfs.createWriteStream({ filename: image.name, metadata });
fs.createReadStream(image.versions[versionName].path).pipe(writeStream);
writeStream.on('close', Meteor.bindEnvironment(file => {
const property = `versions.${versionName}.meta.gridFsFileId`;
// Convert ObjectID to String. Because Meteor (EJSON?) seems to convert it to a
// LocalCollection.ObjectID, which GFS doesn't understand.
this.collection.update(image._id, { $set: { [property]: file._id.toString() } });
this.unlink(this.collection.findOne(image._id), versionName); // Unlink file by version from FS
}));
});
},
interceptDownload(http, image, versionName) {
const _id = (image.versions[versionName].meta || {}).gridFsFileId;
if (_id) {
const readStream = gfs.createReadStream({ _id });
readStream.on('error', err => { throw err; });
readStream.pipe(http.response);
}
return Boolean(_id); // Serve file from either GridFS or FS if it wasn't uploaded yet
}
From now we can store/serve files to/from GridFS. But what will happen if we decide to delete an image? An Image document will be deleted, but a GridFS record will stay in db forever! That's not what we want, right?
So let's fix this by adding onAfterRemove
hook:
onAfterRemove(images) {
images.forEach(image => {
Object.keys(image.versions).forEach(versionName => {
const _id = (image.versions[versionName].meta || {}).gridFsFileId;
if (_id) gfs.remove({ _id }, err => { if (err) throw err; });
});
});
}
Here's a final code:
import { Meteor } from 'meteor/meteor';
import { FilesCollection } from 'meteor/ostrio:files';
import Grid from 'gridfs-stream';
import { MongoInternals } from 'meteor/mongo';
import fs from 'fs';
let gfs;
if (Meteor.isServer) {
gfs = Grid(
MongoInternals.defaultRemoteCollectionDriver().mongo.db,
MongoInternals.NpmModule
);
}
export const Images = new FilesCollection({
collectionName: 'images',
allowClientCode: false,
debug: Meteor.isServer && process.env.NODE_ENV === 'development',
onBeforeUpload(file) {
if (file.size <= 10485760 && /png|jpg|jpeg/i.test(file.extension)) return true;
return 'Please upload image, with size equal or less than 10MB';
},
onAfterUpload(image) {
// Move file to GridFS
Object.keys(image.versions).forEach(versionName => {
const metadata = { versionName, imageId: image._id, storedAt: new Date() }; // Optional
const writeStream = gfs.createWriteStream({ filename: image.name, metadata });
fs.createReadStream(image.versions[versionName].path).pipe(writeStream);
writeStream.on('close', Meteor.bindEnvironment(file => {
const property = `versions.${versionName}.meta.gridFsFileId`;
// If we store the ObjectID itself, Meteor (EJSON?) seems to convert it to a
// LocalCollection.ObjectID, which GFS doesn't understand.
this.collection.update(image._id, { $set: { [property]: file._id.toString() } });
this.unlink(this.collection.findOne(image._id), versionName); // Unlink files from FS
}));
});
},
interceptDownload(http, image, versionName) {
// Serve file from GridFS
const _id = (image.versions[versionName].meta || {}).gridFsFileId;
if (_id) {
const readStream = gfs.createReadStream({ _id });
readStream.on('error', err => { throw err; });
readStream.pipe(http.response);
}
return Boolean(_id); // Serve file from either GridFS or FS if it wasn't uploaded yet
},
onAfterRemove(images) {
// Remove corresponding file from GridFS
images.forEach(image => {
Object.keys(image.versions).forEach(versionName => {
const _id = (image.versions[versionName].meta || {}).gridFsFileId;
if (_id) gfs.remove({ _id }, err => { if (err) throw err; });
});
});
}
});
if (Meteor.isServer) {
Images.denyClient();
}
Meteor-Files | Support | Try ostr.io |
---|---|---|
If you found this package useful, — star it at GitHub and support our open source contributions with a monthly pledge, or submit a one-time donation via PayPal | Monitoring, Analytics, WebSec, Web-CRON and Pre-rendering for a website |