Hi! I’m working on a Netlify Function where we take form data for a job application (including a file upload) and pass the data on to a third-party API for use in their system. I was following along with this handy post (thanks!) —
— but seem to have run into a situation where the data in the file is not handled properly (for example, PDFs turn up with blank content, though ASCII metadata appears to be at least partly intact), at least when using the Netlify CLI; I have yet to try on a deploy preview. Writing to a local directory confirms that the issue isn’t with the third party API. Is there something I’m missing when working with these files? Example code below (note that I’ve also attempted to work with the Buffer data, with identical results).
Netlify site name, if you need it: transformdataio
Fetch function to call the Netlify Function:
const data = new FormData(form);
fetch('/.netlify/functions/apply', {
method: 'POST',
body: data,
}).then(res => {
if (!res.ok && res.status !== 406) {
throw new Error('oh no');
}
return res.json();
}).then(data => {
if (Array.isArray(data.missingRequiredFields) && data.missingRequiredFields.length > 0) {
console.log(data);
showMissingFields(data.missingRequiredFields);
} else {
showConfirmationMessage(data.message);
}
}).catch(err => {
showWarningMessage('Something went wrong; please try again.');
}).finally(() => {
submitButton.removeAttribute('disabled');
});
And here’s our Netlify Function:
const path = require('path');
const os = require('os');
const fs = require('fs');
const request = require('request-promise-native');
const Busboy = require('busboy');
const apiKey = Buffer.from(`${process.env.ASHBY_API_KEY}:`, 'ascii').toString('base64');
const apiUrl = 'https://api.ashbyhq.com/applicationForm.submit';
const REQUIRED_FIELD_NAMES = [
'_systemfield_email',
'_systemfield_name',
'_systemfield_resume'
];
const parseMultipartForm = (event) => {
return new Promise((resolve) => {
const fields = {};
const busboy = new Busboy({
headers: event.headers
});
busboy.on(
'file',
(fieldname, filestream, filename, transferEncoding, mimeType) => {
const saveTo = path.join(os.tmpdir(), path.basename(fieldname));
filestream.pipe(fs.createWriteStream(saveTo));
fields[fieldname] = {
filename,
filepath: saveTo
};
}
);
busboy.on('field', (fieldname, value) => {
fields[fieldname] = value;
});
busboy.on('finish', () => {
resolve(fields);
});
busboy.write(event.body);
});
};
exports.handler = async (event, context) => {
const fields = await parseMultipartForm(event);
const fieldSubmissions = [];
const missingRequiredFields = [];
// @see https://developers.ashbyhq.com/reference#applicationformsubmit for construction details
for (const [path, value] of Object.entries(fields)) {
if (path === 'jobPostingId' || path === 'g-recaptcha-response') {
continue;
}
if (path === '_systemfield_resume') {
if (value.filename === '') {
missingRequiredFields.push(path);
}
fieldSubmissions.push({
path,
value: 'resume_1'
});
} else {
if (REQUIRED_FIELD_NAMES.includes(path) && value === '') {
missingRequiredFields.push(path);
}
fieldSubmissions.push({ path, value });
}
}
if (missingRequiredFields.length > 0) {
return {
statusCode: 406, // "Not Acceptable"
body: JSON.stringify({
message: 'missing one or more required fields',
missingRequiredFields
})
}
}
const { filename, filepath } = fields._systemfield_resume;
const options = {
'method': 'POST',
'url': apiUrl,
'headers': {
'Content-Type': 'multipart/form-data',
'Authorization': `Basic ${apiKey}`
},
formData: {
'applicationForm': JSON.stringify({ fieldSubmissions }),
'resume_1': {
'value': fs.createReadStream(filepath),
'options': {
'filename': filename,
'contentType': null
}
},
'jobPostingId': fields.jobPostingId
}
};
return await request(options)
.then((res) => {
console.log(res);
return {
statusCode: 200,
body: JSON.stringify({ message: `Thanks, ${fields._systemfield_name}!` })
};
})
.catch((err) => {
console.error('ASHBY_ERROR', err);
return {
statusCode: 500,
body: JSON.stringify({ message: 'Something went wrong; please try again.' })
};
});
};
Any guidance you might have would be greatly appreciated! (Worst case I’m willing to try to convert this to use Netlify Forms & a submission-created
event to forward everything, but we don’t need any of this data stored in Netlify’s systems.)