diff --git a/functions/index.js b/functions/index.js index dc838dc45..9616e946e 100644 --- a/functions/index.js +++ b/functions/index.js @@ -20,6 +20,10 @@ const deleteUserFunctionHandler = async (_, { auth }) => { try { const userId = auth.uid; + if (!userId) { + throw new Error("Could not retrieve the user's unique ID."); + } + const updates = {}; const userResumesDataSnapshot = await admin @@ -31,7 +35,9 @@ const deleteUserFunctionHandler = async (_, { auth }) => { const userResumes = userResumesDataSnapshot.val(); if (userResumes) { Object.keys(userResumes).forEach(async (resumeId) => { - updates[`resumes/${resumeId}`] = null; + if (resumeId) { + updates[`resumes/${resumeId}`] = null; + } }); } @@ -48,48 +54,111 @@ const deleteUserFunctionHandler = async (_, { auth }) => { await admin.database().ref().update(updates); } + const storageUserFolderPath = `users/${userId}/`; + await admin.storage().bucket().deleteFiles({ + prefix: storageUserFolderPath, + }); + return true; } catch (error) { throw new functions.https.HttpsError('internal', error.message); } }; -exports.deleteUser = functions - .runWith({ memory: '256MB' }) - .https.onCall(deleteUserFunctionHandler); +/** + * Tries to navigate the page to a given URL. + * + * @param {puppeteer.Page} page Page. + * @param {string} url URL to navigate page to. + * @param {puppeteer.PuppeteerLifeCycleEvent} waitUntil When to consider navigation succeeded. + * @returns {Promise} Returns null if no error occurred, otherwise returns the error message. + */ +const tryGotoPage = async (page, url, waitUntil) => { + let httpResponse; -exports.printResume = functions - .runWith({ memory: '1GB' }) - .https.onCall(async ({ id, type }, { auth }) => { - if (!id) { - throw new functions.https.HttpsError( - 'invalid-argument', - 'The function must be called with argument "id" containing the resume ID.', - ); + try { + httpResponse = await page.goto(url, { + waitUntil, + }); + } catch (error) { + return `page.goto (waitUntil: "${waitUntil}") threw an error with message "${error.message}"`; + } + + if (httpResponse === null) { + return `page.goto (waitUntil: "${waitUntil}") returned a null response`; + } + + if (!httpResponse.ok()) { + return `page.goto (waitUntil: "${waitUntil}") returned a response with HTTP status ${httpResponse.status()} "${httpResponse.statusText()}"`; + } + + return null; +}; + +/** + * Creates a page and navigates to a given URL. + * + * @param {puppeteer.Browser} browser Browser. + * @param {string} url URL to navigate to. + * @returns {Promise<{page: puppeteer.Page, errors: string[]}>} Returns an object with the page if no error occurred, otherwise returns an object with the list of error messages. + */ +const gotoPage = async (browser, url) => { + const errors = []; + + const waitUntilArray = ['networkidle0', 'networkidle2']; + for (let index = 0; index < waitUntilArray.length; index++) { + /* eslint-disable no-await-in-loop */ + const waitUntil = waitUntilArray[index]; + + const page = await browser.newPage(); + await page.setCacheEnabled(false); + + const error = await tryGotoPage(page, url, waitUntil); + if (!error) { + return { page, errors: null }; } - if (!type) { - throw new functions.https.HttpsError( - 'invalid-argument', - 'The function must be called with argument "type" containing the type of resume.', - ); - } + errors.push(error); + await page.close(); + } - if (!auth) { - throw new functions.https.HttpsError( - 'failed-precondition', - 'The function must be called while authenticated.', - ); - } + return { page: null, errors }; +}; +const printResumeFunctionHandler = async ({ id, type }, { auth }) => { + if (!id) { + throw new functions.https.HttpsError( + 'invalid-argument', + 'The function must be called with argument "id" containing the resume ID.', + ); + } + + if (!type) { + throw new functions.https.HttpsError( + 'invalid-argument', + 'The function must be called with argument "type" containing the type of resume.', + ); + } + + if (!auth) { + throw new functions.https.HttpsError( + 'failed-precondition', + 'The function must be called while authenticated.', + ); + } + + try { const browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'], }); - const page = await browser.newPage(); - await page.goto(BASE_URL + id, { - waitUntil: 'networkidle0', - }); + + const url = BASE_URL + id; + const { page, errors } = await gotoPage(browser, url); + if (errors && errors.length > 0) { + throw new Error(errors.join(' - ')); + } + await timeout(6000); await page.emulateMediaType('print'); let pdf; @@ -124,4 +193,15 @@ exports.printResume = functions await browser.close(); return Buffer.from(pdf).toString('base64'); - }); + } catch (error) { + throw new functions.https.HttpsError('internal', error.message); + } +}; + +exports.deleteUser = functions + .runWith({ memory: '256MB' }) + .https.onCall(deleteUserFunctionHandler); + +exports.printResume = functions + .runWith({ memory: '1GB' }) + .https.onCall(printResumeFunctionHandler);