Loopback : Rename file from before remote method using context - node.js

I want to rename file from before remote hook using context.
container.beforeRemote('upload', function (context, res, next) {
/////rename file
}
Can anyone tell me how can i access files from this?

I don't know if it's possible to do it before because we haven't unpacked the multipart form yet.
The afterRemote hooks contains enough information to rename the file if you really need to. Here's an example built on top of loopback's default storage example
app.start = function() {
// Adding an operation hook which renames the recently uploaded file
var container = app.dataSources.storage.models.container;
container.afterRemote('upload', (context, res, next) => {
// The file object is stored in the res param
let file = res.result.files.file[0];
// Get the filepath of our datasource, in this case `storage`.
let root = container.dataSource.settings.root;
// Get the full path of the file we just uploaded
// root/containerName/filename.ext
let filePath = path.resolve(root, file.container, file.name);
// aand rename
fs.rename(filePath, path.resolve(root, file.container, 'newFile.txt'), () => console.log('renamed!'));
});
return app.listen(function() {
app.emit('started');
var baseUrl = app.get('url').replace(/\/$/, '');
console.log('Web server listening at: %s', baseUrl);
if (app.get('loopback-component-explorer')) {
var explorerPath = app.get('loopback-component-explorer').mountPath;
console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
}
});
};

Here is a boot script which will rename the file using a UUID (You need to install UUID package). You can apply other logic such as timestamp etc for renaming the file.
var uuid = require('uuid-v4');
module.exports = function(app) {
var uuid = require('uuid-v4');
module.exports = function(app) {
app.dataSources.storage.connector.getFilename = function(origFilename, req, res) {
var origFilename = origFilename.name;
var parts = origFilename.split('.'),
extension = parts[parts.length - 1];
return uuid() + '.' + extension;
}
}
}

Related

Express request no longer available on fileStream.on('close')

I have an implementation of a POST router in node.js with express. The body of the post is a list of files, and the idea is that these files should be zipped and a link to the resulting zip file should be returned. I'm trying to use 'express-session' to store the progress in terms of number of files zipped as well as the file name of the zip upon completion. Updating the number of files zipped (req.session.current) works fine, but for some reason I can't set the file name (req.session.zipFile) in the session when the fileStream closes. I'm suspecting that the req object is no longer valid by the time fileStream.on('close') is reached, but I'm not sure how to handle that. I can't access the session data without a request.
This is the complete router implementation:
const express = require('express');
const router = express.Router();
const fs = require('fs');
const ZIP_FILES_PATH = require('../constants');
router.post('/', function(req, res, next){
const json = JSON.parse(req.body.data);
const path = json["base"];
if (!path || path.length === 0){
res.render("error", {msg: "JSON missing 'base'"})
return;
}
const files = json["files"];
if (!files || files.length === 0){
res.statusCode = 400;
res.send("JSON missing 'files'");
return;
}
if (!fs.existsSync(path)) {
res.statusCode = 400;
res.send("Directory '" + path + "' does not exist");
return;
}
try{
var sessData = req.session;
sessData.total = files.length ;
sessData.current = 0;
sessData.zipFile = '';
zipFiles(path, files, req, res);
}catch(error){
res.statusCode = 500;
res.send("The files could not be zipped");
return;
}
res.render("zipping")
});
module.exports = router;
const zipFiles = (path, files, req, res) => {
const zipFile = require('crypto').createHash('md5').update(path).digest("hex") + "_" + new Date().getTime() + ".zip";
const archiver = require('archiver');
const fileStream = fs.createWriteStream(ZIP_FILES_PATH + zipFile);
const archive = archiver('zip', {
gzip: true,
zlib: { level: 9 }
});
archive.on('error', function(err) {
throw err;
});
fileStream.on('close', function() {
req.session.zipFile = "/download?file=" + zipFile; //Doesn't stick!
});
archive.pipe(fileStream);
files.map(file => zipSingleFile(path, file, req, archive));
archive.finalize();
}
const zipSingleFile = (path, file, req, archive) => {
fs.existsSync(path + "/" + file) ? archive.file(path + "/" + file, { name: file }) : null;
req.session.current = req.session.current + 1;
}
zipFiles() is asynchronous. That means it doesn't block and finishes some time later. So, in the flow of your request handler, you call
res.render("zipping")
before zipFiles() finishes. And, res.render() will send the response and close the output stream.
If you want to wait to call res.render() until you're done with the zipFiles() call, then you need to either call it from within zipFiles() when it's all done or you need to add a callback to zipFiles() so it can communicate back when it's done.
Also, is there any reason you can't set req.session.zipFile = "/download?file=" + zipFile; before you call res.render()?
Also, depending upon how you have your session configured (which you don't show us any code for), just doing this:
req.session.zipFile = "/download?file=" + zipFile;
may not be enough to actually save that value into the session. You may also have to call .save() on the session.
And, you are setting:
req.session.zipFile
at the end of your zipping process so any requests that arrive from that session before your zipping finishes won't see that session variable set either (this seems like a concurrency problem to me).

Restify JsonClient URL x Path

When I create a JsonClient in node I do the following:
var client = restify.createJsonClient({
url: 'https://www.domain.com:4321/api'
});
Once I've done that, I make calls like so:
client.post('/service/path', { });
Which seems right. I expect that the path called would be something like https://www.domain.com:4321/api/service/path. However, what is happening is that the client is throwing away the /api base path and calling https://www.domain.com:4321/service/path.
I don't get it - I'm inserting the client URL into a config file, so that I can change hosts without any hassle; Now that I need a base path, I need to change the code as well as the config.
If you put a wrapper around the restify JsonClient stuff you could do it with minimal code change and the config would, I think, work the way you want it.
Create a library file myClient.js
'use strict';
var restify = require('restify');
var jsonClient = null;
module.exports = {
createJsonClient: function(opts){
var opts = opts || {};
var url = opts.url;
var parts = url.split('/');
var main_url = parts[0] + '//' + parts[2];
var basePath = parts[3] ? parts[3] : '';
jsonClient = restify.createJsonClient({url: main_url});
return {
get: function(path, cb){
var adjusted_path = '/' + basePath + path;
jsonClient.get(adjusted_path, function(err2, req2, res2, obj2){
return cb(err2, req2, res2, obj2);
});
}
}
}
}
Then use it like this.
var myClientWrapper = require('./lib/myClient');
var client = myClientWrapper.createJsonClient({url: 'http://localhost:8000/api'});
client.get('/service/path/one', function(err, req, res, obj){
if(err){
console.log(err.message);
return;
}
console.log(res.body);
});
It could use some more error checking and the url parsing is a little brittle, but it does work. I tried it out. Of course, I only wrapped the get function but you can see how it would work for the others.

Modify image obtained from loopback-component-storage

I am using loopback for storing Image to the server.
I want to modify the file name of the file before getting saved to the server.
Also I want to convert it to another thumbnail form before getting saved.
Here is how I am doing.
At client side
Upload.upload(
{
url: '/api/containers/container_name/upload',
file: file,
fileName: "demoImage.jpg",
//Additional data with file
params:{
orderId: 1,
customerId: 1
}
});
At Server Side I am receiving the query "params" but not getting the "File Name"
My Storage model name is container
Container.beforeRemote('upload', function(ctx, modelInstance, next) {
//OUPTUTS: {orderId:1, customerId:1]}
console.log(ctx.req.query);
//Now I want to change the File Name of the file.
//But not getting how to do that
next();
})
How to change the File name of the File getting saved at the server?
I figured it out.
We have to define a custom function getFileName in boot/configure-storage.js.
Suppose my datasource for loopback-component-storage is presImage.
server/boot/configure-storage.js
module.exports = function(app) {
//Function for checking the file type..
app.dataSources.presImage.connector.getFilename = function(file, req, res) {
//First checking the file type..
var pattern = /^image\/.+$/;
var value = pattern.test(file.type);
if(value ){
var fileExtension = file.name.split('.').pop();
var container = file.container;
var time = new Date().getTime();
var query = req.query;
var customerId = query.customerId;
var orderId = query.orderId;
//Now preparing the file name..
//customerId_time_orderId.extension
var NewFileName = '' + customerId + '_' + time + '_' + orderId + '.' + fileExtension;
//And the file name will be saved as defined..
return NewFileName;
}
else{
throw "FileTypeError: Only File of Image type is accepted.";
}
};
}
common/models/container.js
Now suppose my container model is container.
module.exports = function(Container) {
Container.afterRemote('upload', function(ctx, modelInstance, next) {
var files = ctx.result.result.files.file;
for(var i=0; i<files.length; i++){
var ModifiedfileName = files[i].name;
console.log(ModifiedfileName) //outputs the modified file name.
} //for loop
next();
}); //afterRemote..
};
Now for converting it images to Thumbnail size
Download the quickthumb
Here is how to use it with loopback.
This code is copied directly from Loopback thumbnail view
common/models/container.js
module.exports = function(Container) {
var qt = require('quickthumb');
Container.afterRemote('upload', function(ctx, res, next) {
var file = res.result.files.file[0];
var file_path = "./server/storage/" + file.container + "/" + file.name;
var file_thumb_path = "./server/storage/" + file.container + "/thumb/" + file.name;
qt.convert({
src: file_path,
dst: file_thumb_path,
width: 100
}, function (err, path) {
});
next();
});
};
Piggybacking on the answer above, this configure-storage enables the file name to be set explicitly via req.params.filename and to default to the existing name if none is provided.
configure-storage.js
module.exports = function(app) {
//Function for checking the file type..
app.dataSources.storage.connector.getFilename = function(file, req, ignoreRes) {
if (!req.params.filename) {
return file.name
}
var fileExtension = file.name.split('.').pop()
return req.params.filename + '.' + fileExtension
};
}

Send PDF file from AngularJS to NodeJS

i need to send a PDF file from angularjs client to NodeJS service.
I did the angularjs service, and when i receive the file its a string like this:
%PDF-1.3
3 0 obj
<</Type /Page
/Parent 1 0 R
/Reso
How can i reconvert this string to PDF in NodeJS?
This is the client code:
var sendByEmail = function () {
$scope.generatingPdf = true;
$('#budget').show();
var pdf = new JsPDF('p', 'pt', 'letter');
var source = $('#budget')[0];
pdf.addHTML(source, 0, 0, function () {
var resultPdf = pdf.output();
BillService.sendByEmail("rbrlnx#gmail.com", resultPdf).then(function () {
});
$('#budget').hide();
});
};
var sendByEmail = function (email, file) {
var deferred = $q.defer();
var data = {
email: email,
file: file
};
BillService.sendByEmail(data, function (result) {
deferred.resolve(result);
}, function () {
deferred.reject();
});
return deferred.promise;
};
The server code controller its empty:
var sendByEmail = function (req, res, next) {
var file = req.body.file;
};
I experimented with this a while ago, and I came up with this. It's not production ready by a long shot maybe you find it useful. It's free of front end libraries (except Angular ofcourse), but assumes you're using Express 4x and body-parser.
The result:
In the browser:
On the server:
What you're seeing:
You're seeing a tiny node server, serving static index.html and angular files, and a POST route receiving a PDF in base64 as delivered by the HTML FileReader API, and saves it to disk.
Instead of saving to disk, you can send it as an email attachment. See for instance here or here for some info on that.
The example below assumes uploading a PDF by a user through a file input, but the idea is the same for all other ways of sending a document to your back end system. The most important thing is to send the pdf data as BASE64, because this is the format that most file writers and email packages use (as opposed to straight up binary for instance..). This also goes for images, documents etc.
How did I do that:
In your HTML:
<div pdfs>Your browser doesn't support File API.</div>
A directive called pdfs:
myApp.directive('pdfs', ['upload', function(upload) {
return {
replace: true,
scope: function() {
files = null;
},
template: '<input id="files" type="file">',
link: function(scope,element) {
element.bind('change', function(evt) {
scope.$apply(function() {
scope.files = evt.target.files;
});
});
},
controller: function($scope, $attrs) {
$scope.$watch('files', function(files) {
//upload.put(files)
if(typeof files !== 'undefined' && files.length > 0) {
for(var i = 0; i<files.length;i++) {
readFile(files[i])
}
}
}, true);
function readFile(file) {
var reader = new FileReader();
reader.addEventListener("loadend", function(evt) {
upload.post({name: file.name, data: reader.result})
})
if(reader.type = 'application/pdf') {
reader.readAsDataURL(file);
}
}
}
}
}]);
A tiny service:
myApp.service('upload', function($http) {
this.post = function(file) {
$http.post('/pdf', file);
}
});
And a node server:
var express = require('express');
var bodyParser = require('body-parser')
var fs = require("fs");
var app = express();
app.use(express.static('.'));
app.use( bodyParser.json({limit: '1mb'}) );
app.post('/pdf', function(req, res){
var name = req.body.name;
var pdf = req.body.data;
var pdf = pdf.replace('data:application/pdf;base64,', '');
res.send('received');
fs.writeFile(name, pdf, 'base64', function(err) {
console.log(err);
});
});
var server = app.listen(3000, function() {
console.log('Listening on port %d', server.address().port);
});

regarding foodme project in github

hello i have a question regarding the foodme express example over github:
code:
var express = require('express');
var fs = require('fs');
var open = require('open');
var RestaurantRecord = require('./model').Restaurant;
var MemoryStorage = require('./storage').Memory;
var API_URL = '/api/restaurant';
var API_URL_ID = API_URL + '/:id';
var API_URL_ORDER = '/api/order';
var removeMenuItems = function(restaurant) {
var clone = {};
Object.getOwnPropertyNames(restaurant).forEach(function(key) {
if (key !== 'menuItems') {
clone[key] = restaurant[key];
}
});
return clone;
};
exports.start = function(PORT, STATIC_DIR, DATA_FILE, TEST_DIR) {
var app = express();
var storage = new MemoryStorage();
// log requests
app.use(express.logger('dev'));
// serve static files for demo client
app.use(express.static(STATIC_DIR));
// parse body into req.body
app.use(express.bodyParser());
// API
app.get(API_URL, function(req, res, next) {
res.send(200, storage.getAll().map(removeMenuItems));
});
i don't understand where is the api folder. it doesn't exist and i don't understand how information is going in and out from there. i can't find it.
can someone please explain this to me?
another question:
there is a resource for the restaurant
foodMeApp.factory('Restaurant', function($resource) {
return $resource('/api/restaurant/:id', {id: '#id'});
});
and in the restaurant controller there is a query:
var allRestaurants = Restaurant.query(filterAndSortRestaurants);
and the following lines:
$scope.$watch('filter', filterAndSortRestaurants, true);
function filterAndSortRestaurants() {
$scope.restaurants = [];
// filter
angular.forEach(allRestaurants, function(item, key) {
if (filter.price && filter.price !== item.price) {
return;
}
if (filter.rating && filter.rating !== item.rating) {
return;
}
if (filter.cuisine.length && filter.cuisine.indexOf(item.cuisine) === -1) {
return;
}
$scope.restaurants.push(item);
});
// sort
$scope.restaurants.sort(function(a, b) {
if (a[filter.sortBy] > b[filter.sortBy]) {
return filter.sortAsc ? 1 : -1;
}
if (a[filter.sortBy] < b[filter.sortBy]) {
return filter.sortAsc ? -1 : 1;
}
return 0;
});
};
the things that isn't clear to me is:
how is that we are giving the query just a function without even activating it.
as i understand we should have passed the query somthing like:
{id: $routeParams.restaurantId}
but we only passed a reference to a function. that doesn't make any sense.
could someone elaborate on this?
thanks again.
var API_URL = '/api/restaurant';
var API_URL_ID = API_URL + '/:id';
var API_URL_ORDER = '/api/order';
These lines are just defining string constants that are plugged into Express further down. They're not a folder.
app.get(API_URL, function(req, res, next) {
res.send(200, storage.getAll().map(removeMenuItems));
});
So this function call to app.get(API_URL... is telling Express "Look out for GET requests that are pointed at the URL (your app's domain)/api/restaurant, and execute this function to handle such a request."
"api" is not a folder.
Every requests will pass through the app.get method.
This method will respond to the routes /api/restaurant as defined in the API_URL variable.

Resources