Download pdf using remote method - javascript

I intend to download a dynamically generated pdf file using a remote method, the file exists at a particular path and I am using return type "file". My implementation is:
customer.downloadFile = function downloadFile(userId, res, cb){
var reader = fs.createReadStream(__dirname + '/../document.pdf');
cb(null, reader, 'application/pdf');
};
customer.remoteMethod(
'downloadFile',
{
isStatic: true,
accepts: [
{ arg: 'id', type: 'string', required: true },
{ arg: 'res', type: 'object', 'http': { source: 'res' } }
],
returns: [
{ arg: 'body', type: 'file', root: true },
{ arg: 'Content-Type', type: 'string', http: { target: 'header' } }
],
http: {path: '/:id/downloadFile', verb: 'get'}
}
);
The issue with the above code is that the browser although displays the beautiful pdf file container, but instead of the file following error is shown:
Please point out as to what is wrong with the code and how to correct.
Got lead from this URL: https://github.com/strongloop/loopback-swagger/issues/34

Got that working with following:
fs.readFile(fileName, function (err, fileData) {
res.contentType("application/pdf");
res.status(200).send(fileData);
if (!err) {
fs.unlink(fileName);
}
else {
cb(err);
}
});

You should use loopback-component-storage to manage downloadable files.
Files are grouped in so-called containers (Basically, a folder with a single level of hierarchy, not more).
How it is done:
Create a model called container for instance.
Create a storage datasource that uses as connector the loopback-component-storage
Give to container model the datasource storage
That's it. You can upload and download files to/from your container.
With a container, you can store files to a local filesystem, or move later to Cloud solutions.

Related

How to manually upload a file with cypress?

I know that there is a plugin for doing this in cypress already. However using it with jar files just corrupts the file and therefore it is useless in my usecase. This is really weird because i tried it with every combination of encoding and it still does not work. Works perfectly fine with other filetypes however.
This is the code i am using (works except with jar files).
cy.fixture('test.jar', 'binary')
.then(Cypress.Blob.binaryStringToBlob)
.then(fileContent => {
cy.get('[data-cy="dropzone"]')
.contains('Jar file')
.parent()
.find('div')
.attachFile({
fileContent,
filePath: 'test.jar',
mimeType: 'application/java-archive',
encoding: 'binary',
lastModified: new Date().getTime(),
subjectType: 'drag-n-drop',
force: true
});
});
I know that you can perform post requests in cypress. Is it possible to perfom the file upload with it after clicking the dropzone element?
cypress-file-upload lacks explicit support for .jar extension, this is the current list of supported extensions
const FILE_EXTENSION = Object.freeze({
JSON: 'json',
JS: 'js',
COFFEE: 'coffee',
HTML: 'html',
TXT: 'txt',
CSV: 'csv',
PNG: 'png',
JPG: 'jpg',
JPEG: 'jpeg',
GIF: 'gif',
TIF: 'tif',
TIFF: 'tiff',
ZIP: 'zip',
PDF: 'pdf',
VCF: 'vcf',
SVG: 'svg',
XLS: 'xls',
XLSX: 'xlsx',
DOC: 'doc',
DOCX: 'docx',
MP3: 'mp3'
});
The default encoding used is therefore utf8. To explicitly pass in the encoding as an option, you need to use raw mode
cy.fixture('sample.jar', 'binary')
.then(Cypress.Blob.binaryStringToBlob)
.then(fileContent => {
cy.get('[data-cy="dropzone"]').attachFile({
fileContent,
filePath: 'sample.jar',
mimeType: 'application/java-archive',
encoding: 'binary',
lastModified: new Date().getTime(),
},
{
subjectType: 'drag-n-drop',
force: true
});
});
Note you need to try both subjectType values, as they trigger different events.
Here's the code that shows which shows which events are triggered with force option
export const EVENTS_BY_SUBJECT_TYPE: Record<Required<HtmlSubjectType>, string[]> = {
[HtmlSubjectType.input]: ['change'],
/**
* #see https://developer.mozilla.org/en-US/docs/Web/API/DragEvent
*/
[HtmlSubjectType.dragAndDrop]: ['dragstart', 'drag', 'dragenter', 'drop', 'dragleave', 'dragend'],
};
If you still get no action, you will need to manually trigger events, e.g chain .trigger('change') after attachFile(). Ideally you can figure out which events from the app source code.
The workaround given by abramenal is incorrect, this is the part of cypress-file-upload that handles the options parameter. If you pass fixture as a string, encoding is ignored
function getFixtureInfo(fixtureInput) {
// Normal mode
// fixture name and options passed separately.
if (typeof fixtureInput === 'string') {
return {
filePath: fixtureInput,
encoding: '',
mimeType: '',
fileName: getFileName(fixtureInput)
};
}
// Raw mode
// If you pass an object, encoding can be specified
return {
filePath: fixtureInput.filePath,
encoding: fixtureInput.encoding || '',
mimeType: fixtureInput.mimeType || '',
fileName: fixtureInput.fileName || getFileName(fixtureInput.filePath),
fileContent: fixtureInput.fileContent,
lastModified: fixtureInput.lastModified
};
}

Upload a excel file with google drive api

I'm having a problem with the google drive API.
I'm trying to upload an excel file with this API, but it's not working. Even copying the google API documentation doesn't work.
Here is a sample of my code:
#Get('teste')
async teste(){
const keys = require(path.resolve('src', 'files', 'api', 'keys'))
const client = new google.auth.JWT(
keys.client_email,
null,
keys.private_key,
['https://www.googleapis.com/auth/drive.metadata.readonly']
)
client.authorize((err, tokens) =>{
if(err){
console.log(err)
return;
} else{
this.gdrun(client)
}
})
}
gdrun(client){
const drive = google.drive({version: 'v3', auth: client});
var fileMetadata = {
name: 'My Report',
mimeType: 'application/vnd.google-apps.spreadsheet'
};
var media = {
mimeType: 'application/vnd.ms-excel',
body: require(path.resolve('src', 'files', 'excel', 'solargroup.xlsx'))
};
drive.files.create({
resource: fileMetadata,
media: media,
fields: 'id'
}, function (err, file: any) {
if (err) {
// Handle error
console.error(err);
} else {
console.log('File Id:', file.id);
}
});
}
I received this error:
I believe your goal as follows.
You want to upload a file (XLSX file) to Google Drive of the service account.
You want to achieve this using the service account with googleapis for Node.js.
From your script, I thought that you might wanted to upload a XLSX file as Google Spreadsheet by converting.
Modification points:
When you want to upload a file to Google Drive, in this case, please use the scope of https://www.googleapis.com/auth/drive instead of https://www.googleapis.com/auth/drive.metadata.readonly.
When you want to upload XLSX file as the XLSX file, the mimeType is application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.
When you want to upload a file, body: require(path.resolve('src', 'files', 'excel', 'solargroup.xlsx')) cannot be used. In this case, please use body: fs.createReadStream(path.resolve('src', 'files', 'excel', 'solargroup.xlsx')). I thought that your error message might be due to this.
When you want to retrieve the file ID of the uploaded file, please modify file.id to file.data.id.
When above points are reflected to your script, it becomes as follows.
Modified script:
From:
const client = new google.auth.JWT(
keys.client_email,
null,
keys.private_key,
['https://www.googleapis.com/auth/drive.metadata.readonly']
)
To:
const client = new google.auth.JWT(
keys.client_email,
null,
keys.private_key,
['https://www.googleapis.com/auth/drive'] // Modified
)
And also, please modify your gdrun() as follows.
gdrun(client){
const drive = google.drive({ version: "v3", auth: client });
var fileMetadata = {
name: "My Report",
mimeType: "application/vnd.google-apps.spreadsheet",
};
var media = {
mimeType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", // Modified
body: fs.createReadStream(path.resolve('src', 'files', 'excel', 'solargroup.xlsx')), // Modified
};
drive.files.create(
{
resource: fileMetadata,
media: media,
fields: "id",
},
function (err, file) {
if (err) {
console.error(err);
} else {
console.log("File Id:", file.data.id); // Modified
}
}
);
}
In this case, please use const fs = require("fs").
Result:
When above script is run, the following result is obtained.
File Id: ###fileId###
Note:
Your script uploads a XLSX file to Google Drive of service account as Google Spreadsheet. In this case, you cannot directly seen the uploaded file at your Google Drive. Because the Google Drive of the service account is different from your Google Drive. When you want to see the uploaded file at your Google Drive, please create a folder on your Google Drive and share the created folder with the email of the service account. And then, please upload the file to the shared folder. By this, you can see the uploaded file on your Google Drive with your browser. For this, please modify fileMetadata as follows.
var fileMetadata = {
name: "My Report",
mimeType: "application/vnd.google-apps.spreadsheet",
parents: ["### folderId ###"], // Please set the folder ID of the folder shared with the service account.
};
In above script, the maximum file size is 5 MB. Please be careful this. When you want to upload a file more than 5 MB, please use resumable upload. Ref
References:
Upload file data
Files: create

Mock different api response based on input with Axios

I'm building some e2e test for my Vuejs application.
The framework I'm using is Nightwatch along with the http library Axios (and the relative plugin for mocking: Axios-mock-adapter) and my current process is to have a file that intercepts all the api, and a file that return the reponse object:
So for example, if I want to mock /api/sources:
mock.onGet(/my.url\/api\/sources/).reply(() =>
[200, ResponseObject.getSources],
);
And in the reponse object file I have:
const getSources = {
files: [
{
id: 'bogus',
fileName: 'bogus',
fileUrl: 'http://bogus.com/1',
size: 400,
uploadedTime: '2018-05-24 10:56:27',
sourceContact: 'boguscontact',
isFolder: false,
}, {
id: 'bogus2',
fileName: 'bogus 2',
fileUrl: 'http://bogus.com/2',
size: 500,
uploadedTime: '2018-05-24 10:56:27',
sourceContact: 'boguscontact',
isFolder: false,
}, {
id: 'bogus3',
fileName: 'bogus 3',
fileUrl: 'http://bogus.com/3',
size: 600,
uploadedTime: '2018-05-24 10:56:27',
sourceContact: 'boguscontact',
isFolder: false,
},
],
};
With this set up I have a very annoying problem:
Sometimes I have to return different object through the same api call, for example, if the file has a property ready and I want to test the user-flow to prepare a file to be ready I need to return the file with ready: false the first time, then I add some parameters, and then I have to return the file with ready: true. How can I do that?
Another example would be getting a single source file. I have the same api call api/source/:sourceId but when the source has ready: true it needs to have more parameters compare if the source has ready: false, but I don't know how to simulate that behaviour without a backend.
Right now the only thing I can do is to have a different response based on the query parameters:
mock.onGet(/dmd\.mocked\/api\/files/).reply((config) => {
if (typeof config.params !== 'undefined') {
switch (config.params.status) {
case 'queued':
return [200, ResponseObject.queuedFilesList];
case 'processing':
return [200, ResponseObject.processingFilesList];
default:
return [506];
}
} else {
return [200, ResponseObject.queuedFilesList];
}
});
but this approach works only if I make the call with different parameters. If I make the call without any parameters I don't know how to diversify the response.

Can I retrieve metadata when using "listobjects" on S3 in node?

I can retrieve basic information using "listObjects" but would like to get each objects metadata without doing another request. At the moment I'm using:
var bucket = new AWS.S3({
params: {
Bucket: 'Bucketname',
Prefix: req.body.params.objectId + '/',
Delimiter: '/'
}
});
bucket.listObjects(function(err, data) {
if (err) {
console.log('Could not load objects from S3');
} else {
res.json(data);
}
});
This returns
CommonPrefixes: Array[0]
Contents: Array[1]
Delimiter: "/"
IsTruncated: false
Marker: ""
MaxKeys: 1000
Name: "encore-storage"
Prefix: "Music/Demos/song.mp3"
But I would love to get the meta-data such as the "ContentType" without making another call for each object.

Cross-domain Update from Sencha Touch 2 to external server

Trying to create a Sencha-Touch-2 app syncing to a Node.js server; code below.
The server uses another port on the same IP, so this is cross-domain.
(The server uses Mongoose to talk to a MongoDB back-end (not shown))
Using a JSONP Proxy as shown can read data from the server but breaks when writing:
"JSONP proxies can only be used to read data".
I guess the JSONP Proxy writer config is just to write the query and isn't used to write sync (save).
Sencha docs state an Ajax proxy can't go cross-domain, even though a
Cross-domain Ext.Ajax/Ext.data.Connection is discussed in the Sencha forums:
http://www.sencha.com/forum/showthread.php?17691-Cross-domain-Ext.Ajax-Ext.data.Connection
I have found several ways to do a (cross-domain) JSON post (e.g. Mobile Application Using Sencha Touch - JSON Request Generates Syntax Error)
but don't know how to integrate this as a writer in a proxy which syncs my store.
Sencha Touch: ScriptTagProxy url for create/update functionality
seems to offer pointers, but this is ajax and apparently unsuited for cross domain.
I've been reading this forum and elsewhere for a couple of days, but I seem to be stuck. Any help would be much appreciated.
Node.js and restify server
var server = restify.createServer({
name: 'Server',
key: fs.readFileSync(root+'/'+'privatekey.pem'),
certificate: fs.readFileSync(root+'/'+'certificate.pem')
});
server.use(restify.bodyParser());
server.use(restify.queryParser());
function getMessages(req, res, next) {
Model.find(function (err,data) {
res.setHeader('Content-Type', 'text/javascript;charset=UTF-8');
res.send(req.query["callback"] + '({"records":' + JSON.stringify(data) + '});');
});
}
function postMessage(req, res, next) {//not yet tested
var obj = new Model();
obj.name = req.params.name;
obj.description = req.params.description;
obj.date = new Date();
obj.save(function (err) {
if (err) throw err;
console.log('Saved.');
res.send('Saved.');
});
}
server.post(/^\/atapp/, postMessage);
server.get(/^\/atapp/, getMessages);
server.listen(port, ipaddr, function() {
console.log('%s: secure Node server started on %s:%d ...', Date(Date.now()), ipaddr, port);
});
Sencha Touch 2
Model
Ext.define('ATApp.model.User', {
extend: 'Ext.data.Model',
config: {
fields: [
{ name: 'name', type: 'string' },
{ name: 'description', type: 'string' },
{ name: 'date', type: 'date' },
{ name: '_id' }
...
Store
Ext.define('ATApp.store.Data', {
extend: 'Ext.data.Store',
requires: [
'ATApp.model.User',
'Ext.data.proxy.JsonP'
],
config: {
autoLoad: true,
model: 'ATApp.model.User',
storeId: 'Data',
proxy: {
type: 'jsonp',
model: 'ATApp.model.User',
url: 'https://192.168.2.45:13017/atapp',
reader: {
type: 'json',
idProperty: '_id',
rootProperty: 'records',
useSimpleAccessors: true
},
writer: {
type: 'json',
allowSingle: false,
encode: true,
idProperty: '_id',
rootProperty: 'records'
...
Controller
onNewDataRecord: function (view) {
console.log('newDataRecord');
var now = new Date();
var record = Ext.create('ATApp.model.User', {
date: now,
name: '..',
description: '..'
});
var store = Ext.data.StoreManager.lookup('Data')
record.setProxy(store.getProxy());
store.add(record);
this.activateEditor(record);
},
...
In Sencha-Touch-2 apps, the browser prohibits cross-domain AJAX calls (which violate the same-origin security policy). This pertains to different domains, different IP addresses and even different ports on the same IP address. JSONP circumvents this partly by fetching/reading data encapsulated in a script tag in a newly initiated HTTP GET message. In this way the Sencha-Touch-2 JSONP proxy can load a store (fetch/read) from a (cross domain) server. However, the JSONP proxy cannot write data. In 1 and 2 an approach is described which I have adapted.
My solution uses the JSONP proxy to fetch data, but not to store (which it can't). Instead, new records, and records to be saved or deleted are communicated with the server in a newly initiated HTTP GET message. Even though only HTTP GET is used, the server accepts message get (described in the question, above), put, del and new. Get is used by JSONP store/proxy load().
Node.js Server
//routes
server.get(/^\/atapp\/put/, putMessage);
server.get(/^\/atapp\/get/, getMessages);
server.get(/^\/atapp\/del/, delMessage);
server.get(/^\/atapp\/new/, newMessage);
function newMessage(req, res, next) {
var obj = new Model(); // Mongoose create new MongoDB object
obj.save(function (err,data) {
var x = err || data;
res.setHeader('Content-Type', 'text/javascript;charset=UTF-8');
res.send(req.query["callback"] + '({"payload":' + JSON.stringify(x) + '});');
}); // send reply to Sencha-Touch 2 callback
}
function putMessage(req, res, next) {
var q = JSON.parse(req.query.data); // no reply: add error recovery separately
var obj = Model.findByIdAndUpdate(q.key,q.val);
}
function delMessage(req, res, next) {
var key = JSON.parse(req.query.data);
Model.findByIdAndRemove(key); // no reply: add error recovery separately
}
Sencha Controller
New
onNewDataRecord: function (view) {
var control = this;
Ext.Ajax.Crossdomain.request({
url: 'https://192.168.2.45:13017/atapp/new',
rootProperty: 'payload',
scriptTag: true, // see [1](http://code.google.com/p/extjsdyntran/source/browse/trunk/extjsdyntran/WebContent/js/3rdparty/Ext.lib.Ajax.js?r=203)
success: function(r) { // process synchronously after response
var obj = r.payload;
var store = Ext.data.StoreManager.lookup('Data');
var key = obj._id // MongoDB document id
store.load(function(records, operation, success) { // add new record to store
var ind = store.findBy(function(rec,id) {
return rec.raw._id==key;
}); // identify record in store
var record = store.getAt(ind);
control.onEditDataRecord(view,record);
}, this);
}
});
Save
onSaveDataRecord: function (view, record) {
rec = {key:record.data.id, val: {}} // save template
var i; for (i in record.modified) rec.val[i]=record.data[i];
var delta = Ext.encode(rec); // only save modified fields
Ext.Ajax.Crossdomain.request({
url: 'https://192.168.2.45:13017/atapp/put',
params: {
data: delta,
},
rootProperty: 'payload',
scriptTag: true, // Use script tag transport
});
},
Delete
onDelDataRecord: function (view, record) {
var key = record.data.id;
Ext.Ajax.Crossdomain.request({ // delete document in db
url: 'https://192.168.2.45:13017/atapp/del',
params: {
data: Ext.encode(key),
format: 'json'
},
rootProperty: 'payload',
scriptTag: true, // Use script tag transport
});
record.destroy(); // delete record from store
},

Categories