Renamed a file uploaded via Angularjs - javascript

In a nutshell, I need to rename files uploaded via Angular and Node and make that new name available to both the back and front ends (I think). What is the best practice for doing so?
I am using a file upload plugin (https://github.com/danialfarid/angular-file-upload) in an Angularjs app. The upload hits a route handled by Nodejs, uploads the file then, on success, angular saves the file name into a users account. However, if two people upload a file of the same name there will be a conflict so I need to rename each file (perhaps by adding a date and time).
My code works as follows:
Angular handles the upload, hitting a route that node can use.
Node move the file into place.
on success, angular saves the file name into the user's account.
It appears I cannot rename the file prior to uploading. If I rename the file via node after it has been moved into place I run into the issue of needing to get that new name back to angular for saving into the database. Not sure of the best way to approach this process.
Here is my current file upload code (works fine, just doesn't rename the file):
In Angular...
$scope.onFileSelect = function ($file) {
var photo = $file[0];
$scope.upload = $upload.upload({
url: '/upload/userphoto',
headers: {'Content-Type': photo.type},
file: photo,
method: 'POST'
}).progress(function (evt) {
//removed for brevity
}).success(function (data, status, headers, config) {
User.currentUser.user_profile_image = photo.name;
User.updateUser();
}).error(function (err) {
//removed for brevity
});
};
The post route, /upload/userphoto is matched in node with:
exports.uploadProfilePhoto = function(req, res) {
var callbacks = {};
callbacks.uploadSuccess = function(){
res.json('success');
};
callbacks.uploadFailure = function(err){
//removed for brevity
};
user.handleUserImageUpload(req.files, callbacks);
};
Which sets some callbacks and then hits handleUserImageUpload() which is:
this.handleUserImageUpload = function(params, callbacks) {
fs.readFile(params.file.path, function(err, data){
if(err){
callbacks.uploadFailure(err);
}
var newPath = path.resolve("./public/fileupload/userphotos/" + params.file.filename);
fs.writeFile(newPath, data, function(err) {
if(err){
callbacks.uploadFailure(err);
}
callbacks.uploadSuccess();
});
});
};
Which, assuming success, takes us back to the success handler of the first bit of Angular code above. I know I can rename the file in the handleUserImageUpload() method with fs.rename() like (just a hard coded example... I would actually take into account the file type and existing name plus add a random string or account identifier):
var renamedPath = path.resolve("./public/fileupload/userphotos/" + "abc123.jpg");
fs.rename(newPath, renamedPath);
But then I don't have that new name available when I go to save the file info into the user's account. I guess I could pass it back in the success callback? Is that the best way or am I missing something?

The solution turned out to be what I suspected... pass the value in the callback. So I added the following to handleUserImageUpload()...
// grab the extension
var fileExtension = '.' + params.file.filename.split('.').pop();
// rename the file with a sufficiently random value and add the file extension back
var renamedFile = Math.random().toString(36).substring(7) + new Date().getTime() + fileExtension;
//set up the new path for use in fs.rename()
var renamedPath = path.resolve("./public/fileupload/userphotos/" + renamedFile);
// Make it so
fs.rename(newPath, renamedPath);
// pass the new name to the callback
callbacks.uploadSuccess(renamedFile);
In the function that created the callbacks, uploadProfilePhoto(), I needed to make one important change, swap json with send (otherwise I would get quotation marks added to the value)
//res.json('success');
res.send(renamedFile);
Then I could access the this name as the data value in my Angular success handler.

Related

Access locally stored file in NodeJS/Express

I am looking to access the locally created PDF in the server(NODEJS/Express) side, i am fairly new to java script and haven't seen any method to access the file.
I think i can use something like ${__dirname}/${user_details.Invoice_No_latest}.pdf but i am not sure if it is correct, any suggestions please
As can seen below the PDF that is generated (using html-pdf) is getting saved locally in the folder D:\Programming\Repo\JavaScript-Project\ec-server\22.pdf'
Once i get access to the file then i will use some thing similar as below code to save it to the database MySql
Snippet of code that i am looking to use afterwards
var jsfile = Buffer.concat(chunks);
var query = 'INSERT INTO `files` SET ?',
values = {
type: 'pdf',
data: jsfile
};
mysql.query(query, values, function(err) {
if (err) throw err; // TODO: improve
// do something after successful insertion
});
just looking if there is any simple way to access annd play with any file in the nodejs/Express that is stored locally there.
I found a way to use a locally stored file using the fs.readFile(/* Arguments Here */) function as shown here:
fs.readFile(`${__dirname}\\` + `${Invoice_No_Actual}` + `.pdf`, (err, data) => {
if (err) res.status(500).send(err);
else {
console.log(data);
res.contentType("blob");
res.send(`data:application/pdf;base64,${new Buffer.from(data).toString("base64")}`);
}
});

PouchDB code won't work without adding attachment

I'm building an app to store information items. Currently there are 3 input fields (Title, description and fileinput).
When I fill in all three fields and call the addItem() function, it works. But when I leave the input field empty, the function doesn't add it to the database.
Is there a way to tell that the file input field is not required?
My javascript code:
function addItem() {
//get file
var inputFile = document.querySelector('#inputFile');
var getFile = inputFile.files[0];
//get info
var title = document.getElementById('itemTitle').value;
var desc = document.getElementById('itemDesc').value;
//add
locallp.put({
_id: new Date().toISOString(),
title: title,
description: desc,
_attachments: {
"file": {
content_type: getFile.type,
data: getFile
}
}
}).then(function(){
console.log("Added to the database");
location.href = "menu.html";
}).catch(function(err){
console.log(err);
});
}
some extra info, I'm using Cordova to build the app. My database is PouchDB and connected via couchperuser to a CouchDB server.
I'd be surprised that your second line (inputFile.files[0]) even works if you don't provide a file. In any case, I'm sure the getFile.type method call will fail if there's no file.
You need some logic in this method so that it does two different things based on whether there's a file supplied or not. If there is one then it does what you have, if there isn't then it doesn't try adding the _attachments and probably skips the whole setting of getFile too.

Meteor synchronous and asynchronous call to read a file

I am new to Meteor. I am using following code to read a file stored at server.
Client side
Meteor.call('parseFile', (err, res) => {
if (err) {
alert(err);
} else {
Session.set("result0",res[0]);
Session.set("result1",res[1]);
Session.set("result2",res[2]);
}
});
let longitude = Session.get("result0");
let latitude = Session.get("result1");
var buildingData = Session.get("result2");
Server Side
Meteor.methods({
'parseFile'() {
var csv = Assets.getText('buildingData.csv');
var rows = Papa.parse(csv).data;
return rows;
}
})
The problem is while I make a call it takes time to send the result back and hence wherever i am using latitude and longitude its giving undefined and page breaks. So, is there any solution to avoid this problem. One of the solution can be to make a synchronous call and wait for result to be returned.
You can make the server method run synchronously using the futures package, which should force the client to wait for the method to complete.
It might look something like this:
Meteor.methods({
'parseFile'() {
var future = new Future();
var csv = Assets.getText('buildingData.csv');
var rows = Papa.parse(csv).data;
future.return(rows);
future.wait();
}
});
This would require you installing the futures package linked above and setting up your includes properly in file containing your Meteor.methods() definitions. You might also look into good error handling inside your method.
UPDATE:
The link to the Future package is an NPM package, which you can read about here. The link above is to the atmosphere package, which looks like an old wrapper package.

MEAN stack how to send a user a file

I have some code on the express controller that looks like this:
// Correct file path and write data
var currentDate = new Date();
var storagePath = path.join(__dirname,'../../public/reports/', 'Risk-Log.csv');
var source = es.readArray(riskLogs);
source.pipe(jsonCSV.csv(options)).pipe(fs.createWriteStream(storagePath));
console.log('Completed csv export to ' + storagePath);
// Send file back
res.sendFile(storagePath);
In my angular view (log.client.view.html) I have the following:
Download CSV
When I click that button... The file gets generated properly, but the file never gets sent back to the user (that they can tell).
If I look at the console debugger I get the following:
Resource {$promise: Object, $resolved: false, $get: function, $save: function, $query: function…}
I haven't done any routing with Angular or anything special since it's hitting the Node (express) controller and generating the file. I'm wondering if this is something that I should be doing in Angular?
What I'm trying to achieve is that when the user clicks that button the CSV downloads.
Any help would be MUCH appreciated.
Since using express res.send() is a buffer.... you have to send in the content type like so:
res.set('Content-Type', 'text/csv');
res.set('Content-Disposition', 'attachment');
res.send(csv); // csv object created with json-csv npm module
Big thanks to all those who commented on this!

Express - Send a page AND custom data to the browser in a single request?

How simultaneously to render a page and transmit my custom data to browser. As i understood it needs to send two layers: first with template and second with JSON data. I want to handle this data by backbone.
As i understood from tutorials express and bb app interact as follows:
res.render send a page to browser
when document.ready trigger jQuery.get to app.get('/post')
app.get('/post', post.allPosts) send data to page
This is three steps and how to do it by one?
var visitCard = {
name: 'John Smit',
phone: '+78503569987'
};
exports.index = function(req, res, next){
res.render('index');
res.send({data: visitCard});
};
And how i should catch this variable on the page- document.card?
I created my own little middleware function that adds a helper method called renderWithData to the res object.
app.use(function (req, res, next) {
res.renderWithData = function (view, model, data) {
res.render(view, model, function (err, viewString) {
data.view = viewString;
res.json(data);
});
};
next();
});
It takes in the view name, the model for the view, and the custom data you want to send to the browser. It calls res.render but passes in a callback function. This instructs express to pass the compiled view markup to the callback as a string instead of immediately piping it into the response. Once I have the view string I add it onto the data object as data.view. Then I use res.json to send the data object to the browser complete with the compiled view :)
Edit:
One caveat with the above is that the request needs to be made with javascript so it can't be a full page request. You need an initial request to pull down the main page which contains the javascript that will make the ajax request.
This is great for situations where you're trying to change the browser URL and title when the user navigates to a new page via AJAX. You can send the new page's partial view back to the browser along with some data for the page title. Then your client-side script can put the partial view where it belongs on the page, update the page title bar, and update the URL if needed as well.
If you are wanting to send a fully complete HTML document to the browser along with some initial JavaScript data then you need to compile that JavaScript code into the view itself. It's definitely possible to do that but I've never found a way that doesn't involve some string magic.
For example:
// controller.js
var someData = { message: 'hi' };
res.render('someView', { data: JSON.stringify(someData) });
// someView.jade
script.
var someData = !{data};
Note: !{data} is used instead of #{data} because jade escapes HTML by default which would turn all the quotation marks into " placeholders.
It looks REALLY strange at first but it works. Basically you're taking a JS object on the server, turning it into a string, rendering that string into the compiled view and then sending it to the browser. When the document finally reaches the browser it should look like this:
// someSite.com/someView
<script type="text/javascript">
var someData = { "message": "hi" };
</script>
Hopefully that makes sense. If I was to re-create my original helper method to ease the pain of this second scenario then it would look something like this:
app.use(function (req, res, next) {
res.renderWithData = function (view, model, data) {
model.data = JSON.stringify(data);
res.render(view, model);
};
next();
});
All this one does is take your custom data object, stringifies it for you, adds it to the model for the view, then renders the view as normal. Now you can call res.renderWithData('someView', {}, { message: 'hi' });; you just have to make sure somewhere in your view you grab that data string and render it into a variable assignment statement.
html
head
title Some Page
script.
var data = !{data};
Not gonna lie, this whole thing feels kind of gross but if it saves you an extra trip to the server and that's what you're after then that's how you'll need to do it. Maybe someone can think of something a little more clever but I just don't see how else you'll get data to already be present in a full HTML document that is being rendered for the first time.
Edit2:
Here is a working example: https://c9.io/chevex/test
You need to have a (free) Cloud9 account in order to run the project. Sign in, open app.js, and click the green run button at the top.
My approach is to send a cookie with the information, and then use it from the client.
server.js
const visitCard = {
name: 'John Smit',
phone: '+78503569987'
};
router.get('/route', (req, res) => {
res.cookie('data', JSON.stringify(pollsObj));
res.render('index');
});
client.js
const getCookie = (name) => {
const value = "; " + document.cookie;
const parts = value.split("; " + name + "=");
if (parts.length === 2) return parts.pop().split(";").shift();
};
const deleteCookie = (name) => {
document.cookie = name + '=; max-age=0;';
};
const parseObjectFromCookie = (cookie) => {
const decodedCookie = decodeURIComponent(cookie);
return JSON.parse(decodedCookie);
};
window.onload = () => {
let dataCookie = getCookie('data');
deleteCookie('data');
if (dataCookie) {
const data = parseObjectFromCookie(dataCookie);
// work with data. `data` is equal to `visitCard` from the server
} else {
// handle data not found
}
Walkthrough
From the server, you send the cookie before rendering the page, so the cookie is available when the page is loaded.
Then, from the client, you get the cookie with the solution I found here and delete it. The content of the cookie is stored in our constant. If the cookie exists, you parse it as an object and use it. Note that inside the parseObjectFromCookie you first have to decode the content, and then parse the JSON to an object.
Notes:
If you're getting the data asynchronously, be careful to send the cookie before rendering. Otherwise, you will get an error because the res.render() ends the response. If the data fetching takes too long, you may use another solution that doesn't hold the rendering that long. An alternative could be to open a socket from the client and send the information that you were holding in the server. See here for that approach.
Probably data is not the best name for a cookie, as you could overwrite something. Use something more meaningful to your purpose.
I didn't find this solution anywhere else. I don't know if using cookies is not recommended for some reason I'm not aware of. I just thought it could work and checked it did, but I haven't used this in production.
Use res.send instead of res.render. It accepts raw data in any form: a string, an array, a plain old object, etc. If it's an object or array of objects, it will serialize it to JSON for you.
var visitCard = {
name: 'John Smit',
phone: '+78503569987'
};
exports.index = function(req, res, next){
res.send(visitCard};
};
Check out Steamer, a tiny module made for this this exact purpose.
https://github.com/rotundasoftware/steamer
Most elegant and simple way of doing this is by using rendering engine (at least for that page of concern). For example use ejs engine
node install ejs -s
On server.js:
let ejs = require('ejs');
app.set('view engine', 'ejs');
then rename desired index.html page into index.ejs and move it to the /views directory. After that you may make API endpoit for that page (by using mysql module):
app.get('/index/:id', function(req, res) {
db.query("SELECT * FROM products WHERE id = ?", [req.params.id], (error, results) => {
if (error) throw error;
res.render('index', { title: results[0] });
});
});
On the front-end you will need to make a GET request, for example with Axios or directly by clicking a link in template index.ejs page that is sending request:
<a v-bind:href="'/index/' + co.id">Click</a>
where co.id is Vue data parameter value 'co' that you want to send along with request

Categories

Resources