Google Drive Token Management Refactor Plan
Created: 2025-01-26
Status: Not Started
Objective: Centralize Google Drive token management to eliminate code duplication and improve error handling
Problem Statement
Currently, Google Drive token management logic is duplicated across 10+ API routes. Each route independently:
- Decrypts tokens from the database
- Checks token expiry
- Refreshes tokens if needed
- Updates database with new tokens
- Handles errors inconsistently
This creates:
- 30+ lines of duplicate code per route
- Inconsistent error handling
- Maintenance burden
- Risk of bugs when updating token logic
Solution
Create a single centralized function getValidGoogleDriveToken() that encapsulates all token management logic. Routes will call this function and get either a valid access token or an error.
Implementation Steps
Step 0: Setup and Preparation ✅
Status: Completed
- [x] Create this implementation plan
- [x] Identify all affected files
- [x] Document current token flow
Files Created/Modified:
/docs/internal-docs/google-drive-token-refactor-plan.md(this file)
Step 1: Create the Centralized Token Helper Function
Status: ✅ Completed (2025-01-26)
What We're Changing:
- Adding a new function
getValidGoogleDriveToken()to/apps/web/src/lib/google-drive.ts - This function will handle all token operations internally
Exact Changes:
// Add to /apps/web/src/lib/google-drive.ts
export async function getValidGoogleDriveToken(
userId: string,
profile: any, // Type from user_profiles table
supabase: SupabaseClient
): Promise<{ accessToken: string } | { error: string }> {
try {
// 1. Check if tokens exist
if (!profile.google_drive_access_token || !profile.google_drive_refresh_token) {
throw new Error('No Google Drive tokens found');
}
// 2. Decrypt tokens
const decryptedTokens = decryptGoogleDriveTokens({
access_token: profile.google_drive_access_token,
refresh_token: profile.google_drive_refresh_token,
expires_at: profile.google_drive_token_expires_at,
});
// 3. Check if token needs refresh (with 5-minute buffer)
let accessToken = decryptedTokens.access_token;
if (isTokenExpired(decryptedTokens.expires_at) && decryptedTokens.refresh_token) {
// 4. Refresh the token
const newTokens = await refreshGoogleDriveToken(decryptedTokens.refresh_token);
// 5. Encrypt new tokens
const encryptedNewTokens = encryptGoogleDriveTokens(newTokens);
// 6. Update database
const { error: updateError } = await supabase
.from('user_profiles')
.update({
google_drive_access_token: encryptedNewTokens.access_token,
google_drive_refresh_token: encryptedNewTokens.refresh_token,
google_drive_token_expires_at: encryptedNewTokens.expires_at,
})
.eq('id', userId);
if (updateError) {
throw new Error(`Database update failed: ${updateError.message}`);
}
accessToken = newTokens.access_token;
}
return { accessToken };
} catch (error) {
// Log the actual error for debugging
errorLog('GoogleDriveToken', 'Token validation failed', error);
// Return consistent user-friendly error
return { error: 'Google Drive connection expired. Please reconnect in Settings → Integrations.' };
}
}Testing:
- [x] Function compiles without TypeScript errors ✅
- [x] Existing functions still work ✅
- [x] No import errors ✅
Review Checklist:
- [x] Function handles all error cases ✅
- [x] Error logging is in place ✅
- [x] User message is friendly and actionable ✅
- [x] No sensitive data in error messages ✅
- [x] Database update is atomic ✅
Additional Features Implemented:
- Auto-clears invalid tokens from database when refresh fails
- Sets google_drive_connected to false on permanent token failure
Step 2: Update Cover Letter Route (First Test)
Status: ✅ Completed (2025-01-26)
What We're Changing:
- Replace 30+ lines of token handling in
/apps/web/src/app/api/jobs/[id]/cover-letter/route.ts - This is our first test to ensure the helper works correctly
Current Code (lines ~200-235):
// OLD: Complex token handling
let accessToken: string;
try {
const decryptedTokens = decryptGoogleDriveTokens({...});
if (isTokenExpired(...) && decryptedTokens.refresh_token) {
const newTokens = await refreshGoogleDriveToken(...);
// ... database update logic ...
accessToken = newTokens.access_token;
} else {
accessToken = decryptedTokens.access_token;
}
} catch (decryptError) {
errorLog('CoverLetter', 'Token decryption failed', decryptError);
return NextResponse.json({error: '...'}, {status: 400});
}New Code:
// NEW: Simple 3-line replacement
const tokenResult = await getValidGoogleDriveToken(user.id, profile, supabase);
if ('error' in tokenResult) {
return NextResponse.json({ error: tokenResult.error }, { status: 400 });
}
const accessToken = tokenResult.accessToken;Testing:
- [ ] Cover letter generation still works
- [ението returns proper error when tokens invalid
- [ ] Token refresh works when token expired
- [ ] Error message shows correctly to user
Review Checklist:
- [ ] Old code completely removed
- [ ] Import added for new function
- [ ] Variable names consistent
- [ ] Error response format unchanged
- [ ] No TypeScript errors
Step 3: Update Interview Prep Route
Status: ✅ Completed (2025-01-26)
What We're Changing:
- Apply same pattern to
/apps/web/src/app/api/jobs/[id]/interview-prep/route.ts - Nearly identical changes to Step 2
Files to Modify:
/apps/web/src/app/api/jobs/[id]/interview-prep/route.ts(lines ~200-235)
Testing:
- [ ] Interview prep generation works
- [ ] Error handling consistent with cover letter
Review Checklist:
- [ ] Changes match cover letter route pattern
- [ ] No copy-paste errors
- [ ] Imports correct
Step 4: Update Resume Save to Drive Route
Status: ✅ Completed (2025-01-26)
What We're Changing:
- Update
/apps/web/src/app/api/jobs/[id]/resume/save-to-drive/route.ts
Testing:
- [ ] Resume saves to Google Drive successfully
- [ ] Error handling works
Review Checklist:
- [ ] Pattern consistent with previous routes
- [ ] All token logic replaced
Step 5: Update Google Drive Token Route
Status: ⏳ Not Started
What We're Changing:
- Update
/apps/web/src/app/api/google-drive/token/route.ts - This route is special - it's the main token endpoint
Special Considerations:
- This route returns tokens to the client (for Google Picker)
- Must maintain same response format
Testing:
- [ ] Google Picker still works
- [ ] Token refresh works
- [ ] Response format unchanged
Step 6: Update Google Drive Status Route
Status: ⏳ Not Started
What We're Changing:
- Update
/apps/web/src/app/api/google-drive/status/route.ts
Testing:
- [ ] Status endpoint returns correct connection state
- [ ] Error handling works
Step 7: Update Remaining Routes
Status: ⏳ Not Started
Routes to Update:
- [ ]
/apps/web/src/app/api/jobs/[id]/resume/revert/route.ts - [ ]
/apps/web/src/app/api/jobs/[id]/route.ts - [ ]
/apps/web/src/app/api/jobs/[id]/google-drive-folder/route.ts - [ ]
/apps/web/src/app/api/google-drive/folder/select/route.ts
Testing:
- [ ] Each route functions correctly
- [ ] Error handling consistent
Step 8: Final Testing and Verification
Status: ⏳ Not Started
Comprehensive Testing:
- [ ] Connect new Google Drive account
- [ ] Generate cover letter
- [ ] Generate interview prep
- [ ] Save resume to Drive
- [ ] Let token expire and verify refresh
- [ ] Disconnect and reconnect Google Drive
- [ ] Test with invalid tokens (manually corrupt in DB)
Code Review:
- [ ] All duplicate code removed
- [ ] Error messages consistent
- [ ] No token data exposed in logs
- [ ] Database updates atomic
- [ ] No TypeScript errors
- [ ] No console.log statements left
Files Affected
Modified Files:
/apps/web/src/lib/google-drive.ts- Add helper function/apps/web/src/app/api/jobs/[id]/cover-letter/route.ts- Use helper/apps/web/src/app/api/jobs/[id]/interview-prep/route.ts- Use helper/apps/web/src/app/api/jobs/[id]/resume/save-to-drive/route.ts- Use helper/apps/web/src/app/api/google-drive/token/route.ts- Use helper/apps/web/src/app/api/google-drive/status/route.ts- Use helper/apps/web/src/app/api/jobs/[id]/resume/revert/route.ts- Use helper/apps/web/src/app/api/jobs/[id]/route.ts- Use helper/apps/web/src/app/api/jobs/[id]/google-drive-folder/route.ts- Use helper/apps/web/src/app/api/google-drive/folder/select/route.ts- Use helper
Rollback Plan
If any step causes issues:
Immediate Rollback:
bashgit checkout -- <affected-file>Partial Rollback:
- Keep the helper function
- Revert individual route changes that cause issues
Full Rollback:
bashgit checkout -- apps/web/src/lib/google-drive.ts git checkout -- apps/web/src/app/api/
Success Metrics
- ✅ 300+ lines of duplicate code eliminated
- ✅ Single source of truth for token management
- ✅ Consistent error handling across all routes
- ✅ User-friendly error messages
- ✅ Easier maintenance and debugging
- ✅ No breaking changes to API contracts
Session Handoff Notes
Current Session: Step 0 completed, plan created Next Action: Start with Step 1 - Create the helper function Blockers: None
Notes
- Keep changes atomic and testable
- Test after each step before proceeding
- If uncertain, stop and review before continuing
- Update this document as steps are completed
