Skip to content

Commit ab8d044

Browse files
committed
chore(deploy): update API endpoint to deployment AKfycbx1hNttxsfo5_LaXTPRN7XJvktexa8dWd38pu7ADZGY-JUzNG7S0p7rFohF-AWReBkxMg
- Updated frontend configuration with new Google Apps Script deployment ID - Updated test script with new endpoint - Updated environment configuration Deployment ID: AKfycbx1hNttxsfo5_LaXTPRN7XJvktexa8dWd38pu7ADZGY-JUzNG7S0p7rFohF-AWReBkxMg Endpoint: https://script.google.com/macros/s/AKfycbx1hNttxsfo5_LaXTPRN7XJvktexa8dWd38pu7ADZGY-JUzNG7S0p7rFohF-AWReBkxMg/exec
1 parent b2d8859 commit ab8d044

File tree

6 files changed

+207
-90
lines changed

6 files changed

+207
-90
lines changed

backend/thinkREDBot.js

Lines changed: 113 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,19 @@ function doOptions(e) {
4848

4949
function 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 ===
137150
function 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 = `
179226
A new job application has been received.
180227
181228
Job ID: ${jobId}
@@ -191,7 +238,7 @@ View the full record in the spreadsheet:
191238
https://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-Za-z0-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-zA-Z0-9\s]/g, '').replace(/\s+/g, '_').substring(0, 50);
237294
}

docs/DEPLOYMENT_MANAGEMENT.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ The new system provides:
2121
## 📁 System Components
2222

2323
### 1. GitHub Actions Workflow (`deploy-backend.yml`)
24+
2425
- Automatically captures new deployment IDs from Google Apps Script
2526
- Updates frontend configuration files
2627
- Commits and pushes changes to trigger frontend rebuild
@@ -29,6 +30,7 @@ The new system provides:
2930
### 2. Local Deployment Scripts
3031

3132
#### `scripts/update-deployment-id.sh`
33+
3234
Updates deployment ID across all configuration files:
3335
```bash
3436
# Update to specific deployment ID
@@ -39,6 +41,7 @@ Updates deployment ID across all configuration files:
3941
```
4042

4143
#### `scripts/deployment-manager.sh`
44+
4245
Manages multiple deployment environments:
4346
```bash
4447
# Show current configuration
@@ -58,11 +61,13 @@ Manages multiple deployment environments:
5861
```
5962

6063
#### Enhanced `backend/deploy.sh`
64+
6165
- Automatically extracts deployment ID from `clasp deploy` output
6266
- Calls update scripts to synchronize frontend configuration
6367
- Provides deployment status and testing instructions
6468

6569
### 3. Frontend Configuration (`frontend/src/config/environment.ts`)
70+
6671
Enhanced to support environment-specific deployment IDs:
6772
```typescript
6873
// Environment-specific deployment IDs
@@ -75,6 +80,7 @@ GOOGLE_APPS_SCRIPT_DEPLOYMENT_ID=AKfycby...
7580
```
7681

7782
### 4. Environment Configuration (`.deployment-config.json`)
83+
7884
Stores deployment IDs for different environments:
7985
```json
8086
{
@@ -152,19 +158,21 @@ Stores deployment IDs for different environments:
152158
## 🧪 Testing
153159

154160
### Automated Testing
161+
155162
The `test-cors-api.sh` script is automatically updated with new deployment IDs:
156163
```bash
157164
./test-cors-api.sh
158165
```
159166

160167
### Manual Testing
168+
161169
1. **Check endpoint status**:
162170
```bash
163171
curl "https://script.google.com/macros/s/AKfycby.../exec?action=test"
164172
```
165173

166174
2. **Test job application flow**:
167-
- Visit: https://thinkredtech.github.io/apply/ui-ux-designer
175+
- Visit: <https://thinkredtech.github.io/apply/ui-ux-designer>
168176
- Submit test application
169177
- Verify in Google Sheets
170178

@@ -179,16 +187,19 @@ The system automatically updates these files:
179187
## 🚨 Troubleshooting
180188

181189
### Frontend Still Points to Old Endpoint
190+
182191
1. Check if GitHub Actions deployment completed successfully
183192
2. Verify frontend rebuild was triggered
184193
3. Manually update using: `./scripts/update-deployment-id.sh NEW_ID`
185194

186195
### Backend Deployment ID Not Captured
196+
187197
1. Check `clasp deploy` output format
188198
2. Verify regex pattern in deployment script
189199
3. Manually extract ID from Google Apps Script console
190200

191201
### Environment Configuration Issues
202+
192203
1. Check `.deployment-config.json` exists and is valid JSON
193204
2. Verify `jq` is installed for JSON manipulation
194205
3. Reset configuration: `rm .deployment-config.json && ./scripts/deployment-manager.sh`

frontend/src/config/environment.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ const getDeploymentId = (): string => {
158158
}
159159

160160
// Final fallback to latest known production deployment ID
161-
return 'AKfycbyQpxAHaosv-kGuveJbboxpn3jnzl3TabvmMlTMBAtn-s4VGbEOAJKVYhndRVMYOpISYw';
161+
return 'AKfycbx1hNttxsfo5_LaXTPRN7XJvktexa8dWd38pu7ADZGY-JUzNG7S0p7rFohF-AWReBkxMg';
162162
};
163163

164164
/**

frontend/src/pages/JobApplicationPage.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,8 @@ const JobApplicationPage = () => {
9797
const validationResult = validateFile(
9898
file,
9999
allowedTypes,
100-
5 * 1024 * 1024
101-
); // 5MB limit
100+
10 * 1024 * 1024
101+
); // 10MB limit (increased from 5MB)
102102

103103
if (!validationResult.isValid) {
104104
setErrors(prev => ({
@@ -578,7 +578,7 @@ const JobApplicationPage = () => {
578578
)}
579579
</div>
580580
<p className="text-xs text-gray-500 mt-1">
581-
PDF, DOC, or DOCX format. Max 5MB.
581+
PDF, DOC, or DOCX format. Max 10MB.
582582
</p>
583583
</div>
584584
<div>
@@ -611,7 +611,7 @@ const JobApplicationPage = () => {
611611
)}
612612
</div>
613613
<p className="text-xs text-gray-500 mt-1">
614-
PDF, DOC, or DOCX format. Max 5MB.
614+
PDF, DOC, or DOCX format. Max 10MB.
615615
</p>
616616
</div>
617617
</div>

0 commit comments

Comments
 (0)