@@ -48,7 +48,19 @@ function doOptions(e) {
4848
4949function doPost ( e ) {
5050 try {
51- const payload = JSON . parse ( e . postData . contents ) ;
51+ // Handle larger payloads for file uploads
52+ let payload ;
53+ try {
54+ payload = JSON . parse ( e . postData . contents ) ;
55+ } catch ( parseError ) {
56+ return createErrorResponse ( 'Invalid JSON payload' ) ;
57+ }
58+
59+ // Validate payload structure
60+ if ( ! payload . action ) {
61+ return createErrorResponse ( 'Missing action in payload' ) ;
62+ }
63+
5264 switch ( payload . action ) {
5365 case 'submitContactForm' :
5466 return handleContactForm ( payload . data ) ;
@@ -58,6 +70,7 @@ function doPost(e) {
5870 return createErrorResponse ( 'Invalid action provided' ) ;
5971 }
6072 } catch ( error ) {
73+ console . error ( 'doPost error:' , error ) ;
6174 return createErrorResponse ( `Server Error: ${ error . message } ` ) ;
6275 }
6376}
@@ -135,47 +148,81 @@ https://docs.google.com/spreadsheets/d/${CONTACT_FORM_SHEET_ID}
135148
136149// === JOB APPLICATION HANDLER ===
137150function handleJobApplication ( data ) {
138- const {
139- jobId, applicationId, name, email, phone,
140- resumeBase64, coverLetterBase64
141- } = data ;
142-
143- if ( ! ( jobId && applicationId && name && email && resumeBase64 ) ) {
144- return createErrorResponse ( 'Missing required fields in job application' ) ;
145- }
146-
147- const resumeParentFolder = DriveApp . getFolderById ( RESUME_PARENT_FOLDER_ID ) ;
148- const jobFolder = getOrCreateSubFolder ( resumeParentFolder , jobId ) ;
149- const applicationFolder = jobFolder . createFolder ( applicationId ) ;
150-
151- const resumeFile = applicationFolder . createFile (
152- Utilities . newBlob ( Utilities . base64Decode ( resumeBase64 ) , 'application/pdf' , `${ name } _Resume.pdf` )
153- ) ;
151+ try {
152+ const {
153+ jobId, applicationId, name, email, phone,
154+ resumeBase64, coverLetterBase64
155+ } = data ;
156+
157+ // Validate required fields
158+ if ( ! ( jobId && applicationId && name && email && resumeBase64 ) ) {
159+ return createErrorResponse ( 'Missing required fields in job application' ) ;
160+ }
154161
155- let coverLetterFile = null ;
156- if ( coverLetterBase64 ) {
157- coverLetterFile = applicationFolder . createFile (
158- Utilities . newBlob ( Utilities . base64Decode ( coverLetterBase64 ) , 'application/pdf' , `${ name } _CoverLetter.pdf` )
159- ) ;
160- }
162+ // Validate base64 data to prevent corruption
163+ if ( ! isValidBase64 ( resumeBase64 ) ) {
164+ return createErrorResponse ( 'Invalid resume file format' ) ;
165+ }
161166
162- const sheet = SpreadsheetApp . openById ( JOB_APPLICATION_SHEET_ID ) . getSheetByName ( 'Applications' ) ;
163- if ( ! sheet ) return createErrorResponse ( 'Sheet "Applications" not found' ) ;
167+ if ( coverLetterBase64 && ! isValidBase64 ( coverLetterBase64 ) ) {
168+ return createErrorResponse ( 'Invalid cover letter file format' ) ;
169+ }
164170
165- sheet . appendRow ( [
166- new Date ( ) ,
167- jobId ,
168- applicationId ,
169- name ,
170- email ,
171- phone || '' ,
172- resumeFile . getUrl ( ) ,
173- coverLetterFile ? coverLetterFile . getUrl ( ) : ''
174- ] ) ;
171+ // Get or create folder structure
172+ const resumeParentFolder = DriveApp . getFolderById ( RESUME_PARENT_FOLDER_ID ) ;
173+ const jobFolder = getOrCreateSubFolder ( resumeParentFolder , jobId ) ;
174+ const applicationFolder = jobFolder . createFolder ( applicationId ) ;
175+
176+ // Create resume file with better error handling
177+ let resumeFile ;
178+ try {
179+ const resumeBlob = Utilities . newBlob (
180+ Utilities . base64Decode ( resumeBase64 ) ,
181+ 'application/pdf' ,
182+ `${ sanitizeFileName ( name ) } _Resume.pdf`
183+ ) ;
184+ resumeFile = applicationFolder . createFile ( resumeBlob ) ;
185+ } catch ( error ) {
186+ console . error ( 'Error creating resume file:' , error ) ;
187+ return createErrorResponse ( 'Failed to save resume file' ) ;
188+ }
175189
176- const subject = `[ThinkRED] New Job Application – ${ name } (${ jobId } )` ;
190+ // Create cover letter file if provided
191+ let coverLetterFile = null ;
192+ if ( coverLetterBase64 ) {
193+ try {
194+ const coverLetterBlob = Utilities . newBlob (
195+ Utilities . base64Decode ( coverLetterBase64 ) ,
196+ 'application/pdf' ,
197+ `${ sanitizeFileName ( name ) } _CoverLetter.pdf`
198+ ) ;
199+ coverLetterFile = applicationFolder . createFile ( coverLetterBlob ) ;
200+ } catch ( error ) {
201+ console . error ( 'Error creating cover letter file:' , error ) ;
202+ // Don't fail the entire submission for cover letter issues
203+ console . warn ( 'Cover letter file creation failed, continuing without it' ) ;
204+ }
205+ }
177206
178- const plainBody = `
207+ // Save to spreadsheet
208+ const sheet = SpreadsheetApp . openById ( JOB_APPLICATION_SHEET_ID ) . getSheetByName ( 'Applications' ) ;
209+ if ( ! sheet ) return createErrorResponse ( 'Sheet "Applications" not found' ) ;
210+
211+ sheet . appendRow ( [
212+ new Date ( ) ,
213+ jobId ,
214+ applicationId ,
215+ name ,
216+ email ,
217+ phone || '' ,
218+ resumeFile . getUrl ( ) ,
219+ coverLetterFile ? coverLetterFile . getUrl ( ) : ''
220+ ] ) ;
221+
222+ // Send notification email
223+ const subject = `[ThinkRED] New Job Application – ${ name } (${ jobId } )` ;
224+
225+ const plainBody = `
179226A new job application has been received.
180227
181228Job ID: ${ jobId }
@@ -191,7 +238,7 @@ View the full record in the spreadsheet:
191238https://docs.google.com/spreadsheets/d/${ JOB_APPLICATION_SHEET_ID }
192239` ;
193240
194- const htmlBody = `
241+ const htmlBody = `
195242<h3>New job application received</h3>
196243<ul>
197244 <li><strong>Job ID:</strong> ${ jobId } </li>
@@ -205,12 +252,17 @@ https://docs.google.com/spreadsheets/d/${JOB_APPLICATION_SHEET_ID}
205252<p><a href="https://docs.google.com/spreadsheets/d/${ JOB_APPLICATION_SHEET_ID } ">View Spreadsheet</a></p>
206253` ;
207254
208- GmailApp . sendEmail ( EMAIL_TO , subject , plainBody , {
209- cc : EMAIL_CC_JOB_APPLY ,
210- htmlBody : htmlBody
211- } ) ;
255+ GmailApp . sendEmail ( EMAIL_TO , subject , plainBody , {
256+ cc : EMAIL_CC_JOB_APPLY ,
257+ htmlBody : htmlBody
258+ } ) ;
212259
213- return createCorsResponse ( { success : true } ) ;
260+ return createCorsResponse ( { success : true } ) ;
261+
262+ } catch ( error ) {
263+ console . error ( 'Job application handling error:' , error ) ;
264+ return createErrorResponse ( `Failed to process job application: ${ error . message } ` ) ;
265+ }
214266}
215267
216268// === UTILITY FUNCTIONS ===
@@ -219,19 +271,24 @@ function getOrCreateSubFolder(parent, name) {
219271 return folders . hasNext ( ) ? folders . next ( ) : parent . createFolder ( name ) ;
220272}
221273
222- function createErrorResponse ( message ) {
223- return createCorsResponse ( { success : false , error : message } ) ;
274+ /**
275+ * Validate base64 string format
276+ */
277+ function isValidBase64 ( str ) {
278+ try {
279+ if ( ! str || typeof str !== 'string' ) return false ;
280+ // Basic base64 validation - should be divisible by 4 and contain valid characters
281+ const base64Regex = / ^ [ A - Z a - z 0 - 9 + \/ ] * = { 0 , 2 } $ / ;
282+ return str . length % 4 === 0 && base64Regex . test ( str ) ;
283+ } catch ( error ) {
284+ return false ;
285+ }
224286}
225287
226- function createCorsResponse ( data ) {
227- const output = ContentService . createTextOutput ( JSON . stringify ( data ) )
228- . setMimeType ( ContentService . MimeType . JSON ) ;
229-
230- // Google Apps Script does not support custom headers for CORS
231- // CORS is automatically handled when the web app is deployed with:
232- // - Execute as: Me (or User accessing the web app)
233- // - Who has access: Anyone
234- // The deployment settings handle CORS automatically
235-
236- return output ;
288+ /**
289+ * Sanitize file names to prevent issues with Google Drive
290+ */
291+ function sanitizeFileName ( name ) {
292+ // Remove special characters and replace spaces with underscores
293+ return name . replace ( / [ ^ a - z A - Z 0 - 9 \s ] / g, '' ) . replace ( / \s + / g, '_' ) . substring ( 0 , 50 ) ;
237294}
0 commit comments