How to execute server side file handling operations in Meteor?

Viewed 1169

I'm storing Word(.docx) files using GridFS on the server. I'd like to be able to merge the documents into one Word file by using the docx-builder NPM package.

Here's how I am uploading the files:

Meteor.methods({
    uploadFiles: function (files) {
      check(files, [Object]);

      if (files.length < 1)
        throw new Meteor.Error("invalid-files", "No files were uploaded");

      var documentPaths = [];

      _.each(files, function (file) {
        ActivityFiles.insert(file, function (error, fileObj) {
          if (error) {
            console.log("Could not upload file");
          } else {
            documentPaths.push("/cfs/files/activities/" + fileObj._id);
          }
        });
      });

      return documentPaths;
    }
})

How can I go about doing this on the server side ? I can only do this server side because the package I'm using requires the fs package which cannot be executed client side.

Here's how I am trying to work on this at the moment. From the client, I'm calling the following method (which is declared as a Meteor.method):

print: function(programId) {
  // Get the program by ID.
  var program = Programs.findOne(programId);
  // Create a new document.
  var docx = new docxbuilder.Document();
  // Go through all activities in the program.
  program.activityIds.forEach(function(activityId) {
    // Create a temporary server side folder to store activity files.
    const tempDir = fs.mkdtempSync('/tmp/');
    // Get the activity by ID.
    var activity = Activities.findOne(activityId);
    // Get the document by ID.
    var document = ActivityFiles.findOne(activity.documents.pop()._id);
    // Declare file path to where file will be read.
    const filePath = tempDir + sep + document.name();
    // Create stream to write to path.
    const fileStream = fs.createWriteStream(filePath);
    // Read from document, write to file.
    document.createReadStream().pipe(fileStream);
    // Insert into final document when finished writinf to file.
    fileStream.on('finish', () => {
      docx.insertDocxSync(filePath);
      // Delete file when operation is completed.
      fs.unlinkSync(filePath);
    });
  });
  // Save the merged document.
  docx.save('/tmp' + sep + 'output.docx', function (error) {
    if (error) {
      console.log(error);
    }
    // Insert into Collection so client can access merged document.
    Fiber = Npm.require('fibers');
    Fiber(function() {
      ProgramFiles.insert('/tmp' + sep + 'output.docx');
    }).run();
  });
}

However, when I'm downloading the final document from the ProgramFiles collection on the client side, the document is an empty Word document.

What's going wrong here ?


I've incorporated @FrederickStark's answer into my code. Just stuck on this part now.


Here's another try:

'click .merge-icon': (e) => {
    var programId = Router.current().url.split('/').pop();
    var programObj = Programs.findOne(programId);
    var insertedDocuments = [];
    programObj.activityIds.forEach(function(activityId) {
      var activityObj = Activities.findOne(activityId);
      var documentObj = ActivityFiles.findOne(activityObj.documents.pop()._id);
      JSZipUtils.getBinaryContent(documentObj.url(), callback);
      function callback(error, content) {
        var zip = new JSZip(content);
        var doc = new Docxtemplater().loadZip(zip);
        var xml = zip.files[doc.fileTypeConfig.textPath].asText();
        xml = xml.substring(xml.indexOf("<w:body>") + 8);
        xml = xml.substring(0, xml.indexOf("</w:body>"));
        xml = xml.substring(0, xml.indexOf("<w:sectPr"));
        insertedDocuments.push(xml);
      }
    });
    JSZipUtils.getBinaryContent('/assets/template.docx', callback);
    function callback(error, content) {
      var zip = new JSZip(content);
      var doc = new Docxtemplater().loadZip(zip);
      console.log(doc);
      setData(doc);
    }


    function setData(doc) {
      doc.setData({
        // Insert blank line between contents.
        inserted_docs_formatted: insertedDocuments.join('<w:br/><w:br/>')
        // The template file must use a `{@inserted_docs_formatted}` placeholder
        // that will be replaced by the above value.
      });

      doc.render();

      useResult(doc);
    }

    function useResult(doc) {
      var out = doc.getZip().generate({
        type: 'blob',
        mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
      });
      saveAs(out, 'output.docx');
    }
2 Answers
Related