Skip to content

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:

typescript
// 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):

typescript
// 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:

typescript
// 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:

  1. /apps/web/src/lib/google-drive.ts - Add helper function
  2. /apps/web/src/app/api/jobs/[id]/cover-letter/route.ts - Use helper
  3. /apps/web/src/app/api/jobs/[id]/interview-prep/route.ts - Use helper
  4. /apps/web/src/app/api/jobs/[id]/resume/save-to-drive/route.ts - Use helper
  5. /apps/web/src/app/api/google-drive/token/route.ts - Use helper
  6. /apps/web/src/app/api/google-drive/status/route.ts - Use helper
  7. /apps/web/src/app/api/jobs/[id]/resume/revert/route.ts - Use helper
  8. /apps/web/src/app/api/jobs/[id]/route.ts - Use helper
  9. /apps/web/src/app/api/jobs/[id]/google-drive-folder/route.ts - Use helper
  10. /apps/web/src/app/api/google-drive/folder/select/route.ts - Use helper

Rollback Plan

If any step causes issues:

  1. Immediate Rollback:

    bash
    git checkout -- <affected-file>
  2. Partial Rollback:

    • Keep the helper function
    • Revert individual route changes that cause issues
  3. Full Rollback:

    bash
    git 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

Built with VitePress