Uploading photographs using MEAN.js - javascript

I’m using this tutorial, which is based on the MEAN stack.
I want to store photographs that users can upload with MongoDB on a server.
included: Angular directive to upload files
create-spot.client.view.html
<div data-ng-controller="SpotsCreateController">
<form class="form-signin" data-ng-submit="create(picFile)" novalidate>
<label>Upload an image</label>
<input type="file" id="articleimage" ng-model="picFile" ng-file-select="" ng-file-change="generateThumb(picFile[0], $files)" multiple name="file" accept="image/*">
<img ng-show="picFile[0].dataUrl != null" ng-src="{{picFile[0].dataUrl}}" class="img-thumbnail" height="50" width="100">
<span class="progress" ng-show="picFile[0].progress >= 0">
<div style="width:{{picFile[0].progress}}%" ng-bind="picFile[0].progress + '%'" class="ng-binding"></div>
</span>
<span ng-show="picFile[0].result">Upload Successful</span>
<input type="submit" class="btn btn-lg btn-primary btn-block" ng-click="uploadPic(picFile)">
<div data-ng-show="error">
<strong data-ng-bind="error"></strong>
</div>
</form>
</div>
view-spot.client.view.html
<div data-ng-controller="SpotsViewController">
<section data-ng-init="findOne()">
<img ng-src="data:image/jpeg;base64,{{spot.image}}" id="image-id" width="200" height="200"/>
</section>
</div>
application.js
var app = angular.module('newApp', ['ngAnimate', 'ngCookies', 'ngResource', 'ngRoute', 'ngSanitize', 'ngTouch', 'ui.bootstrap', 'users', 'spots']);
spots.create.client.controller.js
angular.module('spots').controller('SpotsCreateController', ['$scope', '$timeout', 'Authentication', 'Spots', '$location'
function($scope, $timeout, Authentication, Spots, $location) {
$scope.authentication = Authentication;
$scope.fileReaderSupported = window.FileReader !== null;
$scope.create = function(picFile) {
var spot = new Spots({
title: this.title,
description: this.description,
image: null
});
spot.$save(function(response) {
$location.path('spots/' + response._id);
}, function(errorResponse) {
$scope.error = errorResponse.data.message;
});
};
$scope.doTimeout = function(file) {
$timeout( function() {
var fileReader = new FileReader();
fileReader.readAsDataURL(file);
fileReader.onload = function(e) {
$timeout(function() {
file.dataUrl = e.target.result;
});
};
});
};
$scope.generateThumb = function(file) {
if (file) {
if ($scope.fileReaderSupported && file.type.indexOf('image') > -1) {
$scope.doTimeout(file);
}
}
};
spot.server.model.js
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var SpotSchema = new Schema({
...
image: {
type: String,
default: '',
required: false
}
});
mongoose.model('Spot', SpotSchema);
spots.server.routes.js
var multiparty = require('connect-multiparty'),
multipartyMiddleware = multiparty();
module.exports = function(app) {
app.route('/api/spots')
.get(spots.list)
.post(users.requiresLogin, multipartyMiddleware, spots.create);
app.route('/api/spots/:spotId')
.get(spots.read)
.put(users.requiresLogin, spots.hasAuthorization, spots.update)
.delete(users.requiresLogin, spots.hasAuthorization, spots.delete);
app.param('spotId', spots.spotByID);
};
spots.server.controller.js
var mongoose = require('mongoose'),
fs = require('fs'),
Spot = mongoose.model('Spot');
exports.create = function(req, res) {
if (req.files.file) {
var file = req.files.file;
}
var spot = new Spot(req.body);
spot.creator = req.user;
fs.readFile(file.path, function (err,original_data) {
if (err) {
return res.status(400).send({
message: getErrorMessage(err)
});
}
var base64Image = original_data.toString('base64');
fs.unlink(file.path, function (err) {
if (err) {
console.log('failed to delete ' + file.path);
} else {
console.log('successfully deleted ' + file.path);
}
});
spot.image = base64Image;
spot.save(function(err) {
if (err) {
return res.status(400).send({
message: getErrorMessage(err)
});
} else {
res.json(spot);
}
});
});
};
What did I do wrong? Please consider that I want to limit the file sizes, and I thought using base64 is good start. The issue is that the photograph is not stored in the database because the controller doesn’t work with the rest.

What exactly is the problem you are experiencing, and at what step is it going wrong?
For Express backend apps I usually use multer middleware for handling file uploads. Also, I create separate routes/controllers for dealing with the files rather than trying to process them at the same time I'm saving the parent object. This allows me to separate the logic nicely and not worry about the parent object not being saved when the file upload fails. You could use the JS API for ng-file-upload to handle that in Angular.
Example routes in Express (we have a "club" with a "logo" image here):
router.post(
'/logo',
ensureAuthenticated, ensureAdmin,
logoCtrl.upload,
logoCtrl.save
);
router.get(
'/:clubId/logo.*',
logoCtrl.stream
);
Example controller methods:
let multer = require('multer');
module.exports = {
/**
* Upload logo
*/
save(req, res, next) {
//Get club and file
let club = req.user.club;
let file = req.file;
//Update
club.logo = {
data: file.buffer,
mimeType: file.mimetype
};
//Save
club.save()
.then(() => {
res.end();
})
.catch(next);
},
/**
* Stream logo
*/
stream(req, res, next) {
let club = req.club;
res.contentType(club.logo.mimeType);
res.send(club.logo.data);
},
/**
* Upload middleware
*/
upload(req, res, next) {
//Create upload middleware
let upload = multer({
storage: multer.memoryStorage(),
limits: {
fileSize: 50000000
}
}).single('logo');
//Use middleware
upload(req, res, next);
}
};
So as you can see, it's quite simple with multer and all you really need is one route for uploading the logo, with two controller methods, one to register the multer middleware and process the file, and the other to save it to the MongoDB (in this case attached to the club in the request).
Just make sure that ng-file-upload uses the same field name for uploading the file as multer is expecting. In the above example that's "logo". If you're unsure, check in the request what your client app is sending to the server and make sure the server app is expecting the same field name.
Let me know if you have further trouble.

You can use formidable and gridfs-stream
//controller
var mongoose = require('mongoose'),
fs = require('fs'),
Spot = mongoose.model('Spot');
exports.create = function(req, res) {
handleRequest(req, function(err, spot) {
if(err) {
return res.status(400).send({
message: getErrorMessage(err)
});
}
res.json(spot);
});
};
function handleRequest(req) {
var spot = new Spot(req.body);
spot.creator = req.user;
var formidable = require('formidable');
var form = new formidable.IncomingForm();
form.parse(req, function(err, fields, files) {
if (err) {
return done(err);
}
var file = files.qqfile;
if (file && file.name && file.name.trim !== '') {
if (file.size > 5000000) {
message = 'file is too large';
}
if (!file.type) {
message = 'file is not an image';
}
if (file.type.indexOf('image/') !== 0) {
message = 'file is not an image type';
}
}
if (message) {
logger.info('Uploading failed', file, message);
return done(message);
}
uploadFile(mongoose.connection, 'Pictures', files.qqfile, require('uuid').v1(), function(err) {
if (err) {
return done(err);
}
if (!data) return done(false, null);
if (typeof data === 'string') {
data = JSON.parse(data);
}
logger.info('[PHOTOS]', 'Uploaded', data.filename);
photo = {
unique_id: token,
name: file.name,
contentType: file.type
};
spot.photos = spot.photos || [];
spot.photos.push(photo);
spot.markModified('photos');
spot.save(done);
});
});
}
function uploadFile(DB, className, data, token, callback) {
var grid = require('gridfs-stream');
var gfs = grid(DB.db, mongoose.mongo);
var writestream = gfs.createWriteStream({
filename: token,
root: className
});
writestream.on('close', function (file) {
return callback(null, file);
});
if (data.path) {
var fs = require('fs');
if (!fs.existsSync(data.path)) {
return callback(false);
}
var pipe = false;
if (data.pipe) {
pipe = data.pipe;
} else {
var fs = require('fs');
if (!fs.existsSync(data.path)) {
return callback(false);
}
var rs = fs.createReadStream(data.path);
pipe = rs.pipe.bind(rs);
}
return pipe(writestream);
} else {
logger.error('[PHOTO] no path', data);
}
callback(false);
}

Related

hasOwnproperty becomes undefined when used with multer for multipart data

My code is as shown below:
var express = require('express');
var mongoose = require('mongoose');
var bodyParser = require('body-parser');
var multer = require('multer');
var user = require('./../model/user');
var path = require('path');
var upload = multer();
var awsUpload = require('./../config/fileUpload.js');
var Promise = require('promise');
var item = require('./../model/items.js');
var item_image = '';
var storage = multer.diskStorage({
destination: function(req, file, callback) {
callback(null, './public/images')
},
filename: function(req, file, callback) {
item_image = file.fieldname + '-' + Date.now() + path.extname(file.originalname);
callback(null, item_image)
}
});
var itemAdd = function(req, res) {
upload = multer({
limits: {
fileSize: 1000000,
files: 1
},
storage: storage,
fileFilter: function(req, file, callback) {
var ext = path.extname(file.originalname)
if (ext !== '.png' && ext !== '.jpg' && ext !== '.gif' && ext !== '.jpeg') {
return callback(res.end('Only images are allowed'), null)
}
callback(null, true);
}
}).single('item_img');
upload(req, res, function(err) {
var foodtruck_id = req.body.foodtruck_id;
var newItem = new item();
var itemList = [];
newItem.item_name = req.body.item_name;
newItem.item_tag = req.body.item_tag;
newItem.item_description = req.body.item_description;
newItem.item_category = req.body.item_category;
newItem.item_discount_price = req.body.item_discount_price;
for (var key in req.body) {
if (req.body.hasOwnProperty(key)) {
if (key == 'item_illustrations') {
newItem.item_illustrations = req.body[key];
}
}
}
newItem.item_stock = req.body.item_status;
newItem.item_price = req.body.item_price;
if ((foodtruck_id) && (foodtruck_id.trim() != '')) {
foodtruck.findById(foodtruck_id.trim(), function(err, foodtrucks) {
if (err)
res.json({
status: '500',
message: 'There is no data available'
});
newItem.save(function(err, savedItem) {
if (!err) {
foodtrucks.item_list.push(savedItem._id);
foodtrucks.save(function(err, truck) {
foodtruck.find({
_id: truck._id
}).populate('item_list').exec(function(err, foodtrucks) {
res.json({
status: '200',
message: 'New item added successfully',
data: foodtrucks
});
});
});
} else {
res.json({
status: '500',
message: 'Error while saving new item'
});
}
});
});
}
});
};
app.js
app.post('/test',itemAddition);
Now what happens here is, when I use req.body.hasOwnProperty with x-www-formurlencoded, it works fine, but whenever I am adding it with multer (multipart-data), it gives me req.body.hasOwnProperty is not a function. Is there any way with which this thing can be solved?
req.body is a prototype-less object: it was created with Object.create(null) and so doesn’t inherit hasOwnProperty from Object.prototype. This is a good thing, because if a user passed a field named hasOwnProperty, they would be able to break your code.
Use the in operator instead, generally:
if (key in req.body) {
But in the case of the loop, you just don’t need a check at all:
for (var key in req.body) {
if (key == 'item_illustrations') {
newItem.item_illustrations = req.body[key];
}
}
And in this particular case, just get the value you want without a loop at all:
newItem.item_illustrations = req.body.item_illustrations;

Promisify an upload with multer

I have some troubles using multer and promises (bluebird).
I try to upload a pdf file in a folder then extract the text inside this pdf with the plugin (textract)
Both of the functions I created works, but I have some trouble in the execution and the promisification of the upload function. here's my code :
pdf-rest.js :
var bodyParser = require('body-parser');
var request = require('request');
var jsonParser = bodyParser.json();
var fs = require('fs');
var Promise = require('bluebird');
var multer = require('multer');
var textract = require('textract');
module.exports = function(app) {
var destination = "uploads";
var filename = "" + Date.now() + ".pdf";
var filePath = "C:\\wamp64\\www\\ebook-stage\\backend\\rest\\uploads\\" + filename;
var storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, destination);
},
filename: function (req, file, cb) {
cb(null, filename);
}
});
app.post('/rest/create_pdf', function (req, res) {
var upload = multer({storage: storage}).single('file');
function uploadFile() {
var uploadFilePromise = new Promise(function (resolve, reject) {
upload(req, res, function (err) {
if (err) {
reject(err);
res.end('error uploading file')
}
else {
res.end('file uploaded');
console.log('fileupload')
}
});
resolve(upload);
});
console.log(uploadFilePromise);
return uploadFilePromise;
}
function textractPdf(path) {
textract.fromFileWithPath(path, function (error, text) {
console.log('textract');
if (text) {
console.log(text);
return text;
}
else {
console.log(error);
return error;
}
});
}
uploadFile().then(textractPdf(filePath));
});
server.js :
var express = require('express');
var app = express();
require('./pdf-rest.js')(app);
app.listen(8080);
I use a button to execute this script in a HTML page, the upload of the pdf in the folder work the first time I click on the button but the textract doesn't.
I know I do something wrong, the execution of the function textractPdf(Path) is done first. I think I didn't promisify correctly my upload function.

How to get the body before uploading file in multer?

In my project the admins have the ability to upload MP3 files and submit parameters to it like song name.
I decided using multer middleware for handling multipart/form-data.
My problem is req.body.gender returns always undefined, because I have to use it inside the uploadSong listener. I want to upload the song when the gender is zero.
index.ejs
<form method="post" action="/acp" role="publish" enctype="multipart/form-data">
<div class="form-group">
<input type="file" name="song" id="song" accept="audio/mpeg">
</div>
<input type="checkbox" name="gender" checked data-toggle="toggle" data-on="Male" data-off="Female">
</form>
app.js
var uploadSong = upload.single('song');
app.post('/acp', isLoggedIn, function (req, res) {
console.log(req.body.gender); // returns "undefined"
if(req.body.gender == 0) { // returns "undefined"
uploadSong(req, res, function (err) {
if (err) {
res.send('uploaded');
return;
}
res.redirect('/');
});
}
});
(A) Not possible with multer.
(B) Use busboy. It uses streams to parse the form data and so you can get form elements values before the file upload and the fields are made available as events.
(C) Another solution (if you prefer using multer) is to use multer but add a header to send the value of the parameter to check before file upload. Headers are available as soon as the request reaches the server.
by using multer form-data parser you can parse form and access req.body before multer starts just register this app middle-ware:
import * as multer from "multer";
// parse form-data
app.use(multer().any());
This is my sample code, it is woking fine, if you need further explanation please let me know. hope helpful.
var Hotel = require('../models/hotel');
var path = require('path');
var multer = require('multer');
var uplodedImages = [];
var storageHotelGallery = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './uploads/hotelGallery');
},
filename: function (req, file, cb) {
console.log(req.body);
var newFileName = Date.now() + path.extname(file.originalname);
req.newFileName = newFileName;
cb(null, newFileName);
uplodedImages.push(newFileName);
}
});
var uploadHotelGallery = multer({ storage: storageHotelGallery}).fields([{ name: 'imgArr', maxCount: 8 }]);
module.exports = function(router) {
// ADD HOTEL BASIC DATA TO CREATE HOTEL OBJECT
router.post('/createHotelStep1', function(req, res) {
if( req.body.name == null || req.body.name == '' ) {
res.json({ success: false, message: "Hotel name required" });
res.end();
}
else if( req.body.addressline1 == null || req.body.addressline1 == '' ) {
res.json({ success: false, message: "Address line 1 is required" });
res.end();
}
else if( req.body.city == null || req.body.city == '') {
res.json({ success: false, message: "City is required" });
res.end();
}
else {
var hotel = new Hotel();
hotel.name = req.body.name;
hotel.addressline1 = req.body.addressline1;
hotel.addressline2 = req.body.addressline2;
hotel.phone = req.body.phone;
hotel.city = req.body.city;
hotel.email = req.body.email;
hotel.save(function(err) {
if (err) {
res.json({ success: false, message: "Unable to Complete Hotel Step 1" });
} else {
res.json({ success: true, message: 'Create Hotel Step 1 Complete', _id : hotel._id });
}
});
}
});
router.post('/createHotelGallery', function (req, res, next) {
uplodedImages = [];
uploadHotelGallery(req, res, function(err) {
if(err) {
res.json({ success: false, message: 'Could not upload images'});
res.end();
}
else {
Hotel.findOne({ _id:req.body._id }).populate('users').exec(function (err, hotel) {
if (err) {
res.json({ success: false, message: 'Could not save uploaded images to database'});
res.end();
}
else {
for(var x=0; x<uplodedImages.length; x++)
hotel.images.push(uplodedImages[x]);
hotel.save();
res.json({ success: true, message: 'Gallery image uploaded' });
res.end();
}
});
}
});
});
return router;
}
This is my sample code, it is woking fine
const upload = multer({
storage,
fileFilter(req, file, cb) {
if(req.body.name===''){
return cb(new Error('Invalid name'), false)
}
const extname = path.extname(file.originalname).toLowerCase() === '.gz'
const mimetype = file.mimetype === 'application/x-gzip'
if (mimetype && extname) {
return cb(null, true)
} else {
return cb(new Error('Invalid mime type'), false)
}
},
})

Getting form data while using ng-file-upload

I am using ng-file-upload to upload images and its title using the MEAN stack. Currently I can save the image but I am unable to fetch the data sent along.
Controller:
module.exports = function ($scope, Upload) {
let vm = this;
vm.uploadImage = function () {
if (vm.file) {
vm.file.upload = Upload.upload({
url: '/uploads/gallery',
method: 'POST',
data: { title: vm.title },
file: vm.file
});
vm.file.upload.then(function (response) {
$timeout(function () {
vm.file.result = response.data;
});
}, function (response) {
if (response.status > 0) { }
vm.errorMsg = response.status + ': ' + response.data;
}, function (evt) {
vm.file.progress = Math.min(100, parseInt(100.0 *
evt.loaded / evt.total));
});
}
}
vm.browseImage = function (file, errFiles) {
vm.file = file;
vm.errFile = errFiles && errFiles[0];
}
}
Route:
router.post('/gallery', (req, res) => {
//multers disk storage settings
let folder = './public/assets/images/gallery/';
let filename = '';
let imageLocation = '';
let thumbLocation = '';
let response = '';
//console.log(req.form);------throws undefined
//console.log(req.body);------throws undefined
let storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, folder)
},
filename: function (req, file, cb) {
var datetimestamp = Date.now();
filename = file.fieldname + '-' + datetimestamp + '.' + file.originalname.split('.')[file.originalname.split('.').length - 1];
imageLocation = folder + filename;
thumbLocation = folder + 'thumb' + filename;
cb(null, filename)
}
});
//multer settings
let upload = multer({
storage: storage
}).single('file');
upload(req, res, function (err) {
if (err) {
res.json({ error_code: 1, err_desc: err });
return;
}
else {
response = { fileCreated: true };
}
})
});
module.exports = router;
How can I get the string in my form in the route?
upload.single(...) is an express request handler. Multiple request handlers can be used with a router matcher such as the 'router.post' function in your code.
Thus, instead of having only a single request handler as you previously did as follows:
router.post('/gallery', (req, res) => {
...
...
}
You can rewrite your router like this:
router.post('/gallery', upload.single('file'), (req, res) => {
...
...
}
..where you use multiple request handlers.
In order to establish that, you should define multer instance outside your initial router matcher and your file should eventually look like this:
//i assume you have these in your file
const express = require("express");
const multer = require("multer");
let router = express.Router();
//multers disk storage settings
const folder = './public/assets/images/gallery/';
const filename = '';
const imageLocation = '';
const thumbLocation = '';
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, folder)
},
filename: function (req, file, cb) {
var datetimestamp = Date.now();
this.filename = file.fieldname + '-' + datetimestamp + '.' + file.originalname.split('.')[file.originalname.split('.').length - 1];
this.imageLocation = folder + filename;
this.thumbLocation = folder + 'thumb' + filename;
cb(null, filename)
}
});
//multer settings
const upload = multer({
storage: storage
});
router.post('/gallery', upload.single(), (req, res) => {
console.log(res.json);
});
module.exports = router;

how to display binary image in html using angularjs?

i am trying to diplay binary data image in angularjs html form.i am geeting two responses.i dono how to avoid that i have enclosed my screetshot.1st one is my response again it passing bad response please some one help me out.i have enclose client and server side controller
client side controller
'use strict';
/**
* #ngdoc object
* #name test1.Controllers.Test1Controller
* #description Test1Controller
* #requires ng.$scope
*/
angular
.module('test1')
.controller('Test1Controller', [
'$scope','$http' ,'$location','$window',
function($scope,$http, $location,$window)
{
$scope.image = {};
var image="download.jpg";
$http.get('*/upload/'+image).success(function(data,status,response)
{
console.log(data);
$scope.image=data;
var testJpg = $scope.image;
document.getElementById("myimage").src = testJpg;
});
}
]);
backend controller
'use strict';
var mongoose = require('mongoose'),
_ = require('lodash');
var Grid = require('gridfs-stream');
Grid.mongo = mongoose.mongo;
var gfs = new Grid(mongoose.connection.db);
exports.create = function(req, res) {
console.log(req.files.filefield);
var part = req.files.filefield;
var writeStream = gfs.createWriteStream({
filename: part.name,
mode: 'w',
content_type:part.mimetype
});
writeStream.on('close', function() {
return res.status(200).send({
message: 'Success'
});
});
writeStream.write(part.data);
writeStream.end();
};
exports.read = function(req, res) {
gfs.files.find({ filename: req.params.filename }).toArray(function (err, files) {
if(files.length===0){
return res.status(400).send({
message: 'File not found'
});
}
res.writeHead(200, {'Content-Type': files[0].contentType});
var readstream = gfs.createReadStream({
filename: files[0].filename
});
var bufs=[];
readstream.on('data', function(data) {
// res.write(data);
bufs.push(data);
}).on('end', function() {
// res.end();
var fbuf = Buffer.concat(bufs);
var base64 = (fbuf.toString('base64'));
//console.log(base64 );
res.end('"data:image/jpeg;base64,' + base64 + '";');
});
readstream.on('error', function (err) {
console.log('An error occurred!', err);
throw err;
});
});
};
<div ng-controller="Test1Controller" >
<img ng-src="" id="myimage" />
</div>

Categories