Express render broken after save to firebase

846 Views Asked by At

I am writing an express app to generate a google map from geo coordinates out of photos. I am attempting to use firebase to save data about the images. The code is fully working except when I save the photo data to firebase it breaks the map rendering on the next page showing connection errors to all my local files in the console like so console errors

So the page is rendering but the map doesn't load and nor do the images. The data I am saving to firebase is actually saving though, and If I remove the function that saves the data to firebase everything works as expected. I think it may have something to do with the way the response is being pushed but I am at a loss. In any other page where I am saving data to firebase it works fine.

Here is the code for the route that is generating the photo data and saving it to firebase:

var express = require('express');
var router = express.Router();
var util = require('util');
var fs = require('fs');
var im = require('imagemagick');
var stormpath = require('express-stormpath');
var _ = require('lodash')
var Firebase = require('firebase');

router.post("/:campaignId", stormpath.loginRequired, function(req, res, next) {
    function gatherImages(files, callback) {

        //accept single image upload
        if (!_.isArray(files)) {
            files = [files];
        }

        var uploads = [];
        var count = 0;
        files.forEach(function(file) {

            fs.exists(file.path, function(exists) {
                if (exists) {
                    var name = req.body[file.originalname];
                    console.log(name);
                    var path = file.path;
                    var upFile = file.name;
                    uploads.push({
                        file: upFile,
                        imgPath: path,
                        caption: name || 'no comment'
                    });
                    count++;
                }
                if (files.length === count) {
                    callback(uploads);
                }
            });

        });

    }

    function getGeoLoc(path, callback) {
        im.readMetadata('./' + path, function(error, metadata) {
            var geoCoords = false;
            if (error) throw error;

            if (metadata.exif.gpsLatitude && metadata.exif.gpsLatitudeRef) {
                var lat = getDegrees(metadata.exif.gpsLatitude.split(','));
                var latRef = metadata.exif.gpsLatitudeRef;
                if (latRef === 'S') {
                    lat = lat * -1;
                }
                var lng = getDegrees(metadata.exif.gpsLongitude.split(','));
                var lngRef = metadata.exif.gpsLongitudeRef;
                if (lngRef === 'W') {
                    lng = lng * -1;
                }
                var coordinate = {
                    lat: lat,
                    lng: lng
                };
                geoCoords = coordinate.lat + ' ' + coordinate.lng;
                console.log(geoCoords);
            }

            callback(geoCoords);
        });
    }

    function getDegrees(lat) {
        var degrees = 0;
        for (var i = 0; i < lat.length; i++) {
            var cleanNum = lat[i].replace(' ', '');
            var parts = cleanNum.split('/');
            var coord = parseInt(parts[0]) / parseInt(parts[1]);
            if (i == 1) {
                coord = coord / 60;
            } else if (i == 2) {
                coord = coord / 3600;
            }
            degrees += coord;
        }
        return degrees.toFixed(6);
    }

    function processImages(uploads, callback) {
        var finalImages = [];
        var count = 0;
        uploads.forEach(function(upload) {
            var path = upload.imgPath;
            getGeoLoc(path, function(geoCoords) {
                upload.coords = geoCoords;
                finalImages.push(upload);
                count++;
                if (uploads.length === count) {
                    callback(finalImages);
                }
            });
        });
    }

    function saveImageInfo(finalImages, callback) {
        var campaignId = req.param('campaignId');
        var user = res.locals.user;
        var count = 0;
        var campaignPhotosRef = new Firebase('https://vivid-fire-567.firebaseio.com/BSB/userStore/' + user.username + '/campaigns/' + campaignId + '/photos');
        finalImages.forEach(function(image) {
            campaignPhotosRef.push(image, function(err) {
                if (err) {
                    console.log(err);
                } else {
                    count++;
                    if (finalImages.length === count) {
                        callback(finalImages);
                    } else {
                        return;
                    }
                }
            });
        });
    }

    if (req.files) {
        if (req.files.size === 0) {
            return next(new Error("Why didn't you select a file?"));
        }

        gatherImages(req.files.imageFiles, function(uploads) {
            processImages(uploads, function(finalImages) {
                saveImageInfo(finalImages, function(finalImages) {
                    var campaignId = req.param('campaignId');
                    console.log(res.req.next);
                    res.render("uploadMapPage", {
                        title: "File(s) Uploaded Successfully!",
                        files: finalImages,
                        campaignId: campaignId,
                        scripts: ['https://maps.googleapis.com/maps/api/js?key=AIzaSyCU42Wpv6BtNO51t7xGJYnatuPqgwnwk7c', '/javascripts/getPoints.js']
                    });
                });
            });
        });

    }

});

module.exports = router;

This is the only file I have written trying to push multiple objects to firebase. This is my first time using Firebase and Stormpath so any help would be greatly appreciated. Also one other thing that may be helpful is the error from the terminal being output when the issue happens:

POST /uploaded/-JapMLDYzPnbtjvt001X 200 690.689 ms - 2719

/Users/jpribesh/Desktop/Code/BanditSignBoss/node_modules/firebase/lib/firebase-node.js:24
?a:null}function Db(a){try{a()}catch(b){setTimeout(function(){throw b;},Math.f
                                                                    ^
TypeError: Property 'next' of object #<IncomingMessage> is not a function
    at fn (/Users/jpribesh/Desktop/Code/BanditSignBoss/node_modules/express/lib/response.js:899:25)
    at EventEmitter.app.render (/Users/jpribesh/Desktop/Code/BanditSignBoss/node_modules/express/lib/application.js:532:5)
    at ServerResponse.res.render (/Users/jpribesh/Desktop/Code/BanditSignBoss/node_modules/express/lib/response.js:904:7)
    at /Users/jpribesh/Desktop/Code/BanditSignBoss/routes/campaigns.js:20:25
    at Array.forEach (native)
    at /Users/jpribesh/Desktop/Code/BanditSignBoss/routes/campaigns.js:16:18
    at /Users/jpribesh/Desktop/Code/BanditSignBoss/node_modules/firebase/lib/firebase-node.js:25:533
    at Db (/Users/jpribesh/Desktop/Code/BanditSignBoss/node_modules/firebase/lib/firebase-node.js:24:165)
    at Ye (/Users/jpribesh/Desktop/Code/BanditSignBoss/node_modules/firebase/lib/firebase-node.js:124:216)
    at Ze (/Users/jpribesh/Desktop/Code/BanditSignBoss/node_modules/firebase/lib/firebase-node.js:123:818)

UPDATE: It seems that the connection errors are inconsistent. Sometimes the images display just fine, sometimes only some of the images get a connection error, and other times everything including the google map script gets a connection error. This is really throwing me off no idea what the issue is. Any help or suggestions is greatly appreciated!

UPDATE 2: I changed the function saving the image data to firebase to use the firebase push function callback (to indicate completion) and added a length check on the forEach loop running to save each image's data. See updated code above. I am now getting the following error for each image that is uploaded in the terminal, but the connection errors are gone:

    Error: Can't set headers after they are sent.
    at ServerResponse.OutgoingMessage.setHeader (http.js:689:11)
    at ServerResponse.header (/Users/jpribesh/Desktop/Code/BanditSignBoss/node_modules/express/lib/response.js:666:10)
    at ServerResponse.send (/Users/jpribesh/Desktop/Code/BanditSignBoss/node_modules/express/lib/response.js:146:12)
    at fn (/Users/jpribesh/Desktop/Code/BanditSignBoss/node_modules/express/lib/response.js:900:10)
    at View.exports.renderFile [as engine] (/Users/jpribesh/Desktop/Code/BanditSignBoss/node_modules/jade/lib/jade.js:325:12)
    at View.render (/Users/jpribesh/Desktop/Code/BanditSignBoss/node_modules/express/lib/view.js:93:8)
    at EventEmitter.app.render (/Users/jpribesh/Desktop/Code/BanditSignBoss/node_modules/express/lib/application.js:530:10)
    at ServerResponse.res.render (/Users/jpribesh/Desktop/Code/BanditSignBoss/node_modules/express/lib/response.js:904:7)
    at /Users/jpribesh/Desktop/Code/BanditSignBoss/routes/campaigns.js:20:25
    at Array.forEach (native)
1

There are 1 best solutions below

0
On BEST ANSWER

OK I finally figured out the issue here. I did a few things to remedy my problem. First I converted the route to use next properly to separate out each part of the route out, it processes the images, then saves, then renders. Here is the updated code from that file:

    var express = require('express');
    var router = express.Router();
    var util = require('util');
    var fs = require('fs');
    var im = require('imagemagick');
    var stormpath = require('express-stormpath');
    var _ = require('lodash')
    var Firebase = require('firebase');

    function processData(req, res, next) {

        function gatherImages(files, callback) {

            //accept single image upload
            if (!_.isArray(files)) {
                files = [files];
            }

            var uploads = [];
            var count = 0;
            files.forEach(function(file) {

                fs.exists(file.path, function(exists) {
                    if (exists) {
                        var name = req.body[file.originalname];
                        console.log(name);
                        var path = file.path;
                        var upFile = file.name;
                        uploads.push({
                            file: upFile,
                            imgPath: path,
                            caption: name || 'no comment'
                        });
                        count++;
                    }
                    if (files.length === count) {
                        callback(uploads);
                    }
                });

            });

        }

        function getGeoLoc(path, callback) {
            im.readMetadata('./' + path, function(error, metadata) {
                var geoCoords = false;
                if (error) throw error;

                if (metadata.exif.gpsLatitude && metadata.exif.gpsLatitudeRef) {
                    var lat = getDegrees(metadata.exif.gpsLatitude.split(','));
                    var latRef = metadata.exif.gpsLatitudeRef;
                    if (latRef === 'S') {
                        lat = lat * -1;
                    }
                    var lng = getDegrees(metadata.exif.gpsLongitude.split(','));
                    var lngRef = metadata.exif.gpsLongitudeRef;
                    if (lngRef === 'W') {
                        lng = lng * -1;
                    }
                    var coordinate = {
                        lat: lat,
                        lng: lng
                    };
                    geoCoords = coordinate.lat + ' ' + coordinate.lng;
                    console.log(geoCoords);
                }

                callback(geoCoords);
            });
        }

        function getDegrees(lat) {
            var degrees = 0;
            for (var i = 0; i < lat.length; i++) {
                var cleanNum = lat[i].replace(' ', '');
                var parts = cleanNum.split('/');
                var coord = parseInt(parts[0]) / parseInt(parts[1]);
                if (i == 1) {
                    coord = coord / 60;
                } else if (i == 2) {
                    coord = coord / 3600;
                }
                degrees += coord;
            }
            return degrees.toFixed(6);
        }

        function processImages(uploads, callback) {
            var finalImages = [];
            var count = 0;
            uploads.forEach(function(upload) {
                var path = upload.imgPath;
                getGeoLoc(path, function(geoCoords) {
                    upload.coords = geoCoords;
                    finalImages.push(upload);
                    count++;
                    if (uploads.length === count) {
                        callback(finalImages);
                    }
                });
            });
        }


        if (req.files) {
            if (req.files.size === 0) {
                return next(new Error("Why didn't you select a file?"));
            }

            gatherImages(req.files.imageFiles, function(uploads) {
                processImages(uploads, function(finalImages) {
                    req.finalImages = finalImages;
                    req.campaignId = req.param('campaignId');
                    next();
                });
            });
        }

    }

    function saveImageInfo(req, res, next) {
        var user = res.locals.user;
        var count = 0;
        var campaignPhotosRef = new Firebase('https://vivid-fire-567.firebaseio.com/BSB/userStore/' + user.username + '/campaigns/' + req.campaignId + '/photos');
        var finalImages = req.finalImages;
        finalImages.forEach(function(image) {
            campaignPhotosRef.push(image, function(err) {
                if (err) {
                    console.log(err);
                } else {
                    console.log('Data saved successfully: ' + image);
                    count++;
                    if (req.finalImages.length === count) {
                        next();
                    }
                }
            });
        });

    }




    router.post("/:campaignId", stormpath.loginRequired, processData, saveImageInfo, function(req, res) {
        res.render("uploadMapPage", {
            title: "File(s) Uploaded Successfully!",
            files: req.finalImages,
            campaignId: req.campaignId,
            scripts: ['https://maps.googleapis.com/maps/api/js?key=AIzaSyCU42Wpv6BtNO51t7xGJYnatuPqgwnwk7c', '/javascripts/getPoints.js']
        });

    });

    module.exports = router;

Then I realized in the tracestack I included in my question part of it was tracing back to another file I was using firebase in. I was using a call to .on() instead of using .once() when pulling my data. After reorganizing my route and changing all my calls to .on to .once for firebase data everything is now working properly. I think the real issue here was the use of .on() on my firebase calls instead of .once() as the .on() watches for events continually rather than .once which obviously only watches for it once.