Search code examples
google-apps-scriptfile-uploadweb-applications

Google Apps Script is corrupting/blanking-out all files uploaded through a form I created. How do I fix this?


I have used a Google Apps Script form to upload receipts into Google Drive for a couple of years without problems. A few months ago, files have started to come across as completely blank or corrupted. While a file will appear in Drive, I can't open it or, if it's a PDF, it's the same number of pages but completely blank. Only text files seem to work.

I understand conceptually that I need to add a function to allow the script to process the file on the front end and then pass it to the server (based on this and this). But I can't seem to get the script right because my structure is sufficiently different that it confuses me, frankly.

Here is my form.html:

    <!doctype html>
     <form id="myForm" align="left">
            Your first name: <input type="text" name="myName"><br><br>
            <input type="file" name="myFile1"><input type="text" name="myReceipt1" placeholder="Vendor (who was paid)...">
            <input type="text" name="myProgram1" placeholder="GenOps, OWP, VSP, etc."><br>
            <input type="text" name="myDesc1" placeholder="Expense Desc (e.g. catering, airfare, etc.)" style="width:300px;"><br>
            <input type="date" name="myDate1" placeholder="Date Charged (yyyy.mm.dd)" style="width:200px;">
            <input type="text" name="myAmt1" placeholder="Amount (dd.cc)"><br>
            
            <input type="file" name="myFile2"><input type="text" name="myReceipt2" placeholder="Vendor (who was paid)...">
            <input type="text" name="myProgram2" placeholder="GenOps, OWP, VSP, etc."><br>
            <input type="text" name="myDesc2" placeholder="Expense Desc (e.g. catering, airfare, etc.)" style="width:300px;"><br>
            <input type="date" name="myDate2" placeholder="Date Charged (yyyy.mm.dd)" style="width:200px;">
            <input type="text" name="myAmt2" placeholder="Amount (dd.cc)"><br>

            
            <input type="submit" value="Upload File(s)" style="background-color:#ffd382"
            
            onclick="this.value='Uploading...';
                    google.script.run.withSuccessHandler(fileUploaded)
                    .uploadFiles(this.parentNode);
                    return false;">
     </form>

<script>
    function fileUploaded(status) {
        document.getElementById('myForm').style.display = 'none';
        document.getElementById('output').innerHTML = status;
    }
</script>
     

Here is my server.gs:

function doGet(e) {
    return HtmlService.createTemplateFromFile('form.html')
        .evaluate() // evaluate MUST come before setting the Sandbox mode
        .setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL)
        .setTitle("AmEx Receipt Upload");8       
}

function uploadFiles(form) {
  
  try {
    
    var dropbox = "Receipts";  //name of Drive folder to save uploaded files
    var folder, folders = DriveApp.getFoldersByName(dropbox);
    
    if (folders.hasNext()) {
      folder = folders.next();
    } else {
      folder = DriveApp.createFolder(dropbox);
    }
    
    var blob = form.myFile1;
    var file = folder.createFile(blob);    
    file.setDescription("Uploaded by " + form.myName);
    file.setName(form.myDate1 + "_" + form.myReceipt1 + "_" + form.myProgram1 + "_" + form.myDesc1 + " - " + form.myAmt1);
    
    var blob = form.myFile2;    
    var file = folder.createFile(blob);    
    file.setDescription("Uploaded by " + form.myName);
    file.setName(form.myDate2 + "_" + form.myReceipt2 + "_" + form.myProgram2 + "_" + form.myDesc2 + " - " + form.myAmt2);

    return "Your receipts have been uploaded. Refresh the page if you have more to upload.";
    
  } catch (error) { 
    return error.toString();
  }      
}

Any help you can render would be greatly appreciated. It seems that every adjustment I make only makes things worse. If you need me to simply the code snippets more than I have, I certainly can.

Thank you in advance!


Solution

  • Modification points:

    • I thought that the issue that the uploaded files are broken might be the same issue with this thread.
    • In order to avoid to break the uploaded file, for example, it is required to convert the file to the byte array and base64, and then, sent to Google Apps Script.
    • But when I saw your script, I thought that in order to use above, it is required to send 2 files and several values, and requierd to modify both Javascript and Google Apps Script. I thought that this might be a bit complicated. So I proposed the modified script as an answer.

    Modified script:

    In this modification, the file is converted to the byte array and sent to Google Apps Script.

    HTML&Javascript side:

    <form id="myForm" align="left">
    Your first name: <input type="text" name="myName"><br><br>
    <input type="file" name="myFile1">
    <input type="text" name="myReceipt1" placeholder="Vendor (who was paid)...">
    <input type="text" name="myProgram1" placeholder="GenOps, OWP, VSP, etc."><br>
    <input type="text" name="myDesc1" placeholder="Expense Desc (e.g. catering, airfare, etc.)" style="width:300px;"><br>
    <input type="date" name="myDate1" placeholder="Date Charged (yyyy.mm.dd)" style="width:200px;">
    <input type="text" name="myAmt1" placeholder="Amount (dd.cc)"><br>
    
    <input type="file" name="myFile2">
    <input type="text" name="myReceipt2" placeholder="Vendor (who was paid)...">
    <input type="text" name="myProgram2" placeholder="GenOps, OWP, VSP, etc."><br>
    <input type="text" name="myDesc2" placeholder="Expense Desc (e.g. catering, airfare, etc.)" style="width:300px;"><br>
    <input type="date" name="myDate2" placeholder="Date Charged (yyyy.mm.dd)" style="width:200px;">
    <input type="text" name="myAmt2" placeholder="Amount (dd.cc)"><br>
    <input type="submit" value="Upload File(s)" style="background-color:#ffd382" onclick="submitValues(this);return false;">
    </form>
    
    <div id = "output"></div>
    
    <script>
    function submitValues(e) {
      e.value = 'Uploading...';
      const files = [e.parentNode.myFile1.files[0], e.parentNode.myFile2.files[0]];
      const object = [...e.parentNode].reduce((o, obj) => Object.assign(o, {[obj.name]: obj.value}), {});
      if (files.some(f => f)) {
        Promise.all(
          files.map(file => new Promise((resolve, reject) => {
            if (file) {
              const fr = new FileReader();
              fr.onload = f => resolve({filename: file.name, mimeType: file.type, bytes: [...new Int8Array(f.target.result)]});
              fr.onerror = err => reject(err);
              fr.readAsArrayBuffer(file);
            } else {
              resolve({});
            }
          }))
        ).then(ar => {
          [object.myFile1, object.myFile2] = ar;
          google.script.run.withSuccessHandler(fileUploaded).uploadFiles(object);
        });
      }
    }
    
    function fileUploaded(status) {
      document.getElementById('myForm').style.display = 'none';
      document.getElementById('output').innerHTML = status;
    }
    </script>
    

    Google Apps Script side:

    In this case, uploadFiles is modified.

    function uploadFiles(form) {
      try {
        var dropbox = "Receipts";  //name of Drive folder to save uploaded files
        var folder, folders = DriveApp.getFoldersByName(dropbox);
        if (folders.hasNext()) {
          folder = folders.next();
        } else {
          folder = DriveApp.createFolder(dropbox);
        }
        
        // --- I modified below script.
        var file1 = form.myFile1;
        if (Object.keys(file1).length > 0) {
          var blob = Utilities.newBlob(file1.bytes, file1.mimeType, file1.filename);  // Modified
          var file = folder.createFile(blob);    
          file.setDescription("Uploaded by " + form.myName);
          file.setName(form.myDate1 + "_" + form.myReceipt1 + "_" + form.myProgram1 + "_" + form.myDesc1 + " - " + form.myAmt1);
        }
        var file2 = form.myFile2;
        if (Object.keys(file2).length > 0) {
          var blob = Utilities.newBlob(file2.bytes, file2.mimeType, file2.filename);  // Modified
          var file = folder.createFile(blob);    
          file.setDescription("Uploaded by " + form.myName);
          file.setName(form.myDate2 + "_" + form.myReceipt2 + "_" + form.myProgram2 + "_" + form.myDesc2 + " - " + form.myAmt2);
        }
        // ---
        
        return "Your receipts have been uploaded. Refresh the page if you have more to upload.";
      } catch (error) { 
        return error.toString();
      }
    }
    

    Note:

    • In this case, the maximum file size is less than 50 MB because the blob is used at Google Apps Script. Please be careful this.
    • In my environment, I could confirm that the proposed script worked. But if the script doesn't work in your environment, can you provide the script for replicating the issue? By this, I would like to confirm it.

    References: