Multer, Multer-s3 not calling callbacks for dynamic key naming - express

I'm trying to pipe a file that I send with FilePond for React, get with on my expressjs and upload to s3 with multer and multer-s3. I have seen tutorials that specify that the best way to name the files dynamically is to declare a callback on the key but if I do not set a value directly, it simply ignores is and the whole multer middleware sends a success message.
Here is what I'm doing in express:
const app = express();
app.use(bodyParser.urlencoded({ extended: true }))
var upload = multer({
storage: multerS3({
s3: s3,
bucket: aws_bucket_name,
ACL: "public-read",
key: (req, file, cb) => {
console.log("This never gets called");
console.log(req.body);
console.log(file);
cb(null, avatars/${req.params.uid});
}
})
});
app.post('/avatar/:uid', upload.single('file'), async (req, res, next) => {
console.log("this gets called")
res.send("uploaded")
});
I'm using ES6, and am doing exactly as the documentation suggests. Any ideas why this might not be working?
Thanks!

Related

Get multerS3 key from controller

I am using multer to store files in Amazon S3. I need to get the file's multerS3 key from my controller module for later access. My routes module:
var express = require('express');
var router = express.Router();
var post_controller = require('../controllers/postController')
var aws = require('aws-sdk');
var multer = require('multer');
var multerS3 = require('multer-s3');
var mongoose = require('mongoose');
const s3 = new aws.S3();
const upload = multer({
storage: multerS3({
s3: s3,
bucket: 'compressor-input',
metadata: function(req, file, cb) {
cb(null, {fieldName: file.fieldname});
},
key: function (req, file, cb) {
cb(null, mongoose.Types.ObjectId())
}
})
});
router.post('/:id/upload', upload.array('item_images', 12), post_controller.images_upload);
MulterS3 doesn't expose any file specific parameters. You should instead use S3.putObject() in your controller and generate keys either in middleware passed to the router.post function or in your controller module. Express-fileupload is useful when using S3.putObject() because it exposes files in the express request object like so: req.files.
req.files will contain all the information about the uploaded file returned by Amazon.
app.post('/upload', upload.array('photos', 3), function(req, res, next) {
console.log(req.files)
res.send('Successfully uploaded ' + req.files.length + ' files!')
})

Multer-s3-transform with Sharp middleware in Express JS route, uploads correctly, but never calls next

I'm using multer-s3-transform to resize my image upload beore uploading to S3. Using Sharp for the resize. The transform does work fine, the image is resized and uploaded to S3 bucket, I can see the file in the bucket. However, the uploadToS3.single('profilePic') middleware never continues to next route which is end route.
What am I doing wrong?
exports.uploadToS3 = multer({
storage: multerS3({
s3: s3,
bucket: S3_BUCKET,
acl: 'public-read',
contentType: multerS3.AUTO_CONTENT_TYPE,
fileFilter: allowOnlyImages,
limits: {
fileSize: 1024 * 250 // We are allowing only 250K
},
shouldTransform: (req, file, cb) => {
// Is this an image file type?
cb(null, /^image/i.test(file.mimetype));
},
transforms: [{
id: 'thumbnail',
key: (req, file, cb) => {
const fn = `${S3_PROFILE_PICS}/${req.body.handle}/${createFilename(req, file)}`;
//console.log('transforms key fn: ', fn);
cb(null, fn);
},
transform: (req, file, cb) => {
//console.log('transform: ', file.originalname);
// Perform desired transformations
const width = parseInt(PROFILE_PIC_W);
const height = parseInt(PROFILE_PIC_H);
cb(null, sharp()
.resize(width, height)
.toFormat('jpeg')
);
}
}]
})
});
Route...
router.post('/register', uploadToS3.single('profilePic'), async (req, res) => {
...
...
});
It's something about the transform and/or Sharp. If I remove the Sharp transform and just upload the incoming image to S3, everything works fine.
Try this to see it work:
router.post('/register', uploadToS3.single('profilePic'), async (req, res, next) => {
...
...
});

Multer-s3 dynamic s3 instance

I'm trying to upload files to my s3 bucket, using multer and multer-s3 for Nodejs. The problem that I have now is that I want to set up my s3 instance dynamically because the s3 account and the bucket depend on my user settings.
I have the following code:
My uploader
var uploader = multer({
storage: multerS3({
s3: function(req, file, cb){
cb(null, new AWS.S3({
// my s3 settings from req.body or getting directly from my db
}))
},
bucket: function (req, file, cb){
cb(null, req.body.bucket)
},
metadata: function (req, file, cb) {
cb(null, {
fieldName: file.fieldname
});
},
key: function (req, file, cb) {
console.log(`Key is ${req.body.prefix + file.originalname}`);
cb(null, req.body.prefix + file.originalname)
}
})
}).single('file');
api.post('/upload', uploader, function (req, res, next) {
if (err)
return res.status(500).json({ message: err });
res.status(200).json({
message: 'uploaded'
});
});
The problem is that multer-s3 doesn't allow a function for s3 option as parameter but an s3 object instead.
How can I aproach this?

Mongoose - FindOne() inside Multer

I am sending formData to my express app like so:
itemFactory.saveItem = function(item, callback){
var formData = new FormData();
for(var i = 0; i < item.photos.length; i++){
formData.append('photos', item.photos[i]);
}
for(var key in item){
formData.append(key, item[key])
}
return $http.post('/api/item/', formData, {
transformRequest: angular.identity,
headers: { 'Content-Type': undefined }
}).success(callback);
};
I am saving a new item to my mongoose DB. Now everything is working perfectly. But I want to detect duplicates using mongoose findOne(), and not just let mongoose handle detecting if a duplicate key exists when writing to the DB. Because my images get uploaded regardless at this stage, if a duplicate key exists or not. Because it only detects the duplicate on save()
The problem now, in my express app, is when I am using findOne(), Multer() has not yet decoded my formData. ex:
router.post('/item', function(req, res){
Vehicle.findOne({ id: String(req.body.id) }, function(error, item){
var storage = multer.diskStorage({...})
var upload = multer({
storage: storage
}).any();
upload(req, res, function(error){
//formData is only available here via req.body
//and not at findOne() stage.
});
});
});
I cannot do the findOne inside the upload because then the files would be uploaded anyway and then only detect a duplicate.
I tried another multer().any() function for getting the formData just after the .post() but that did not seem to work. I don't think I can do this:
var detectItem = multer().any()
detectItem(req, res, function(){
Vehicle.findOne({ id: String(req.body.id) }, function(error, item){
var storage = multer.diskStorage({...})
var upload = multer({
storage: storage
}).any();
upload(req, res, function(error){
//formData is only available here via req.body
//and not at findOne() stage.
});
})
It does not seem to like me using a multer function inside a multer function.
Any advice?
You can use fileFilter option to control which files are accepted.
It could be something like this:
function filFilter (req, file, cb) {
Vehicle
.findOne({ id: req.body.id })
.then(item => {
cb(null, !item); // Skip if item exists (passing false skips file)
})
}
You could separate them into two middlewares
app.post('/item', function(req, res, next){
//this middleware is used to check duplicate
Vehicle.findOne({id:req.body.id}).then(function(item){
if(item) res.end() //if item existed, send response directly
else next() //if item not existed, continue to next middleware
})
}, function(req, res){
var storage = multer.diskStorage({...})
var upload = multer({ storage: storage }).any();
upload(req, res, function(error){
// ...
});
})

Multer isn't passing in express put route

I'm trying to upload an image to the file system with Multer. Please take a look at the relevant data in my route:
const
..
multer = require('multer'),
..;
const storage = multer.diskStorage({
destination: function (req, file, callback) {
callback(null, './uploads');
},
filename: function (req, file, callback) {
callback(null, req.params.id + file.originalname);
}
}),
upload = multer({storage: storage}).single('profilePic');
router.put(
'/:id',
middleware.isLoggedIn,
(req, res, next) => {
User
.findByIdAndUpdate(
req.params.id, req.body.user,
(err, updatedUser) => {
if (err) {
return req.flash('error', err.message);
}
upload(req, res, (err) => {
if (err) {
eval(locus);
return req.flash('error', err.message);
}
updatedUser = req.body.user;
eval(locus);
//redirect show page
res.redirect('/dashboard/profile/' + req.params.id + '/edit');
});
});
});
module.exports = router;
When I look at updatedUser the first thing I see is
{ profilePic: 'data:image/jpeg;base64,....} what am I doing wrong? It's not even updating the page now that I have the upload function in here. What I really want to do is get the destination to work on s3 but I need to get this to save first.
So, this is a the most basic example of uploading an image using multer:
var express = require('express')
var multer = require('multer')
var app = express()
var storage = multer.diskStorage({
// define where the file should be uploaded, else it will be uploaded to the system temp dir
destination: function (req, file, cb) {
// ./uploads should be created beforehand
cb(null, './uploads')
},
// define "filename", else a random name will be used for the uploaded file
filename: function (req, file, cb) {
cb(null, file.fieldname + '-' + file.originalname)
}
})
var upload = multer({ storage: storage })
// pic is the name of image field in the form
app.put('/profile', upload.single('pic'), function (req, res, next) {
console.log(req.file)
res.send('Uploaded')
})
app.listen(3000)
And here is an example curl command to upload an image from the file system to the above app:
curl -X PUT -F 'pic=#/projects/eg/foto.png' localhost:3000/profile
Make sure the example works fine, to ensure you understand how multer handles file uploads, and that the issue is not with multer.
That said and done, User.findByIdAndUpdate seems to be storing the image data as a base64 encoded string somewhere; I have no idea what User.findByIdAndUpdate connects to. It is beyond the domain of multer.
Someone on our Gitter channel (https://gitter.im/expressjs/express) might be able to suggest something. Join us there.