0

I am having trouble loading a firebase storage document in node js (preferably in binary) so that I can generate a docxtemplater document on it. I'm quite new to docxtemplater and would really like to use it for my webapp

Is this something that can be done?

Below is the code I get but I dont think it's loading the document from my firebase storage properly:

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const {Storage} = require('@google-cloud/storage');
var PizZip = require('pizzip');
var Docxtemplater = require('docxtemplater');
admin.initializeApp();
const BUCKET = 'gs://mpcwapp.appspot.com';

    const https = require('https');
    
    const storage = new Storage({
    projectId: 'myapp' });
    const cors = require('cors')({origin: true});

exports.test2 = functions.https.onCall((data, context) => {
// The error object contains additional information when logged with JSON.stringify (it contains a properties object containing all suberrors).
function replaceErrors(key, value) {
    if (value instanceof Error) {
        return Object.getOwnPropertyNames(value).reduce(function(error, key) {
            error[key] = value[key];
            return error;
        }, {});
    }
    return value;
}

function errorHandler(error) {
    console.log(JSON.stringify({error: error}, replaceErrors));

    if (error.properties && error.properties.errors instanceof Array) {
        const errorMessages = error.properties.errors.map(function (error) {
            return error.properties.explanation;
        }).join("\n");
        console.log('errorMessages', errorMessages);
        // errorMessages is a humanly readable message looking like this :
        // 'The tag beginning with "foobar" is unopened'
    }
    throw error;
}

//Load the docx file as a binary
let file_name = 'input.docx';
const myFile =storage.bucket(BUCKET).file(file_name);
var content =  myFile.createReadStream();

var zip = new PizZip(content);
var doc;
try {
    doc = new Docxtemplater(zip);
} catch(error) {
    // Catch compilation errors (errors caused by the compilation of the template : misplaced tags)
    errorHandler(error);
}

//set the templateVariables
doc.setData({
    first_name: 'John',
    last_name: 'Doe',
    phone: '0652455478',
    description: 'New Website'
});

try {
    // render the document (replace all occurences of {first_name} by John, {last_name} by Doe, ...)
    doc.render();
}
catch (error) {
    // Catch rendering errors (errors relating to the rendering of the template : angularParser throws an error)
    errorHandler(error);
}
var buf = doc.getZip()
             .generate({type: 'nodebuffer'});

buf.pipe(myFile.createWriteStream());

});

Any help will be appreciated I am really stuck.

Donnald Cucharo
  • 3,866
  • 1
  • 10
  • 17
Deji James
  • 367
  • 6
  • 20

1 Answers1

1

First, if you're deploying your code logic in Firebase Functions, make sure it's inside your list of exports.

I tried to reproduce the behavior of your code and noticed that the root cause of the error is because of this part in your code:

var content =  myFile.createReadStream();
var zip = new PizZip(content);

pizzip appears to accept a Buffer input according to this documentation. However, createReadStream() returns ReadableStream so there's a mismatch between the required parameters.

There are two solutions I can think of:

  1. First, download and store the file (in /tmp). Then read the file using fs.readFileSync().
  2. Skip saving the file to the file system and get the buffer of the file object.

For the 2nd option, you need to understand how streams work. This answer can give you a good head start. As example, you can get the buffer from ReadableStream like this:

const remoteFile = storage.bucket("bucket-name").file("file-name")
const readable = remoteFile.createReadStream()

var buffers = [];
readable.on('data', (buffer) => {
  buffers.push(buffer)
});
readable.on('end', () => {
  var buffer = Buffer.concat(buffers);  
  var zip = new PizZip(buffer);
  var doc;
  try {
      doc = new Docxtemplater(zip);
  } catch(error) {
      errorHandler(error);
  }

  // ... rest of your code
});
Donnald Cucharo
  • 3,866
  • 1
  • 10
  • 17
  • thank you @Dondi. It seems that a straightforward way to generate the document successfully is to get the buffer of `myFile` straight from the firebase storage instead of creating a ReadableStream in the first place or have i misunderstood? – Deji James Jun 15 '21 at 02:06
  • @DejiJames you'll have to process the ReadableStream first to get the buffer, or as you said, get the buffer directly as discussed in this [GitHub answer](https://github.com/googleapis/nodejs-storage/issues/676#issuecomment-487710921). – Donnald Cucharo Jun 15 '21 at 03:07
  • @DejiJames I updated my answer. Let me know if it helps. – Donnald Cucharo Jun 15 '21 at 03:48
  • 1
    thanks a million i think it works now. I just keep getting the error `doc.pipe is not a function` but I am working to see where I made the error now. – Deji James Jun 15 '21 at 04:48
  • Hi OP. If my answer was useful, please consider upvoting it. If it answered your question, then accept it. That way others know that you've been (sufficiently) helped. Also see [What should I do when someone answers my question](https://stackoverflow.com/help/someone-answers)? – Donnald Cucharo Jun 16 '21 at 22:13
  • 1
    Hi @Dondi just did. Thank you so much. I am really getting the hang on node js and firebase firestore off this project alone. – Deji James Jun 16 '21 at 22:23