Skip to content

API Error Handling Issues Tracker

Created: 2025-08-25
Status: In Progress
Last Updated: 2025-08-25

Overview

Comprehensive review of error handling across all API routes in the web application. Issues are categorized by severity with specific file locations, problem descriptions, and proposed solutions.


🔴 CRITICAL Issues (2)

1. Google Drive Token Validation Missing Error Handling

File: /api/google-drive/status/route.ts
Lines: 113-125
Status: ⏳ Pending Review

Problem:

  • validateFolderAccess call is not wrapped in try-catch
  • If folder validation throws an exception, the entire status check could crash
  • No graceful degradation if Google Drive API is temporarily unavailable

Current Code Issue:

typescript
const folderValidation = await validateFolderAccess(accessToken, profile.google_drive_folder_id)
// No error handling if validateFolderAccess throws

Impact:

  • Status endpoint could return 500 instead of degraded status
  • Users can't see partial status information
  • Critical for Google Drive integration reliability

Proposed Solution:

typescript
let folderValidation = { valid: false, error: 'Unable to validate' };
try {
  folderValidation = await validateFolderAccess(accessToken, profile.google_drive_folder_id);
} catch (error) {
  errorLog('GoogleDriveStatus', 'Folder validation failed', error);
  // Continue with degraded status instead of crashing
}

Decision: [ ] Required [ ] Not Required [ ] Needs Discussion


2. Database Transaction Rollbacks Missing in Admin Create Free User

File: /api/admin/create-free-user/route.ts
Lines: 93-102, 115
Status: ⏳ Pending Review

Problem:

  • Stripe subscription created successfully (line 83-92)
  • If database update fails (line 105-114), Stripe subscription remains but user profile not updated
  • Partial rollback exists for Stripe failures but not for database failures after Stripe success

Current Code Issue:

typescript
// Stripe subscription created
stripeSubscription = await stripe.subscriptions.create({...});

// Database update - if this fails, Stripe subscription orphaned
const { error: profileError } = await supabaseAdmin
  .from('user_profiles')
  .update({...})
  .eq('id', userId);

if (profileError) {
  errorLog('AdminCreateFreeUser', 'Error updating user profile', profileError);
  // Don't fail the whole operation, user is created
  // PROBLEM: Stripe subscription exists but not linked to user
}

Impact:

  • User has Stripe subscription but app doesn't recognize it
  • Manual intervention required to link subscription
  • Potential billing issues

Proposed Solution:

typescript
if (profileError) {
  errorLog('AdminCreateFreeUser', 'CRITICAL: Failed to link subscription to user', {
    userId,
    customerId: stripeCustomer.id,
    subscriptionId: stripeSubscription.id,
    error: profileError
  });
  
  // Attempt to cancel the subscription since we can't track it
  try {
    await stripe.subscriptions.cancel(stripeSubscription.id);
    await stripe.customers.del(stripeCustomer.id);
  } catch (rollbackError) {
    errorLog('AdminCreateFreeUser', 'Failed to rollback Stripe resources', rollbackError);
  }
  
  return NextResponse.json(
    { error: 'Failed to complete user setup. Please try again.' },
    { status: 500 }
  );
}

Decision: [ ] Required [ ] Not Required [ ] Needs Discussion


🟠 HIGH Issues (3)

3. Profile Fetch Failures Silently Ignored in Job Analysis

File: /api/jobs/analyze/route.ts
Lines: 220-236
Status: ⏳ Pending Review

Problem:

  • Profile/resume fetch wrapped in try-catch but errors not logged
  • Analysis continues without resume data
  • User not informed that analysis is incomplete

Current Code Issue:

typescript
try {
  const { data: userProfile } = await supabase
    .from('user_profiles')
    .select('current_resume_json')
    .eq('id', user.id)
    .single();
  
  if (userProfile?.current_resume_json) {
    userResumeContent = userProfile.current_resume_json;
  }
} catch (error) {
  // Silently continues without resume data
  // No error logging or user notification
}

Impact:

  • Job analysis quality degraded without user awareness
  • Debugging issues difficult without error logs
  • User might think full analysis was performed

Proposed Solution:

typescript
} catch (error) {
  errorLog('JobAnalysis', 'Failed to fetch user resume for analysis', error);
  // Include warning in response
  analysisMetadata.warnings = ['Analysis performed without resume data'];
}

Decision: [ ] Required [ ] Not Required [ ] Needs Discussion


4. Google Drive Folder Creation Without Cleanup

Files:

  • /api/jobs/[id]/cover-letter/route.ts (lines 320-325)
  • /api/jobs/[id]/interview-prep/route.ts (lines 310-315)
    Status: ⏳ Pending Review

Problem:

  • Folder creation failures return 500 but don't clean up partial state
  • Job records may have invalid folder references
  • Subsequent document generation attempts fail

Current Code Issue:

typescript
const folder = await createJobFolder(job, accessToken);
if (!folder) {
  return NextResponse.json(
    { error: 'Failed to create folder in Google Drive' },
    { status: 500 }
  );
  // No cleanup of job record or retry logic
}

Impact:

  • Job records corrupted with invalid folder IDs
  • Users can't generate documents for those jobs
  • Manual database cleanup required

Proposed Solution:

typescript
const folder = await createJobFolder(job, accessToken);
if (!folder) {
  // Clear the invalid folder reference
  await supabase
    .from('saved_jobs')
    .update({ google_drive_folder_id: null })
    .eq('id', jobId);
    
  return NextResponse.json(
    { error: 'Failed to create folder. Please try again.' },
    { status: 500 }
  );
}

Decision: [ ] Required [ ] Not Required [ ] Needs Discussion


5. Stripe Session Creation Not Wrapped in Try-Catch

File: /api/stripe/checkout/route.ts
Lines: 77-99
Status: ⏳ Pending Review

Problem:

  • Direct Stripe API call without error wrapping
  • Could expose sensitive Stripe error details to client
  • No sanitization of error messages

Current Code Issue:

typescript
// Create Stripe Checkout session
const session = await stripe.checkout.sessions.create({
  customer: customerId,
  mode: 'subscription',
  // ... configuration
});
// If this throws, raw Stripe error goes to client

Impact:

  • Security risk: Stripe internal errors exposed
  • Poor error messages for users
  • Difficult to debug without proper logging

Proposed Solution:

typescript
let session;
try {
  session = await stripe.checkout.sessions.create({...});
} catch (stripeError) {
  errorLog('StripeCheckout', 'Failed to create checkout session', {
    error: stripeError,
    customerId,
    priceId
  });
  
  return NextResponse.json(
    { error: 'Unable to create checkout session. Please try again.' },
    { status: 500 }
  );
}

Decision: [ ] Required [ ] Not Required [ ] Needs Discussion


🟡 MEDIUM Issues (3)

6. Database Query Error Logging Inconsistency

File: /api/usage/stats/route.ts
Lines: 76-80, 112-114
Status: ⏳ Pending Review

Problem:

  • Some database errors have empty catch blocks
  • Inconsistent error logging patterns
  • Makes debugging production issues difficult

Current Code Issue:

typescript
if (summaryError) {
  // Empty - no error logging
} else {
  // Process success case only
}

Impact:

  • Silent failures in production
  • Difficult to diagnose usage tracking issues
  • Inconsistent with other routes

Proposed Solution:

typescript
if (summaryError) {
  errorLog('UsageStats', 'Failed to fetch usage summary', summaryError);
  // Continue with partial data or return error based on severity
}

Decision: [ ] Required [ ] Not Required [ ] Needs Discussion


7. Email Sending Failures Don't Affect Response

File: /api/support/submit/route.tsx
Lines: 97-100, 111-114
Status: ⏳ Pending Review

Problem:

  • Email failures are logged but response still shows success
  • Users think support request failed when only email notification failed
  • Support request is saved but team not notified

Current Code Issue:

typescript
try {
  await sendEmail({...});
} catch (emailError) {
  errorLog('Support', 'Failed to send email', emailError);
  // Still returns success response
}

return NextResponse.json({ 
  success: true,
  message: 'Support request submitted'
});

Impact:

  • User confusion about request status
  • Support team might miss urgent requests
  • False success indication

Proposed Solution:

typescript
let emailSent = true;
try {
  await sendEmail({...});
} catch (emailError) {
  errorLog('Support', 'Failed to send email notification', emailError);
  emailSent = false;
}

return NextResponse.json({ 
  success: true,
  message: emailSent 
    ? 'Support request submitted successfully'
    : 'Request saved but notification failed. We will still review your request.',
  emailSent
});

Decision: [ ] Required [ ] Not Required [ ] Needs Discussion


8. Rate Limiting Missing Retry Information

File: /api/auth/resend-verification/route.ts
Lines: 55-78
Status: ⏳ Pending Review

Problem:

  • Rate limit errors don't include retry-after information
  • Users don't know how long to wait
  • Could lead to repeated failed attempts

Current Code Issue:

typescript
if (recentAttempts >= 3) {
  return NextResponse.json(
    { error: 'Too many attempts. Please try again later.' },
    { status: 429 }
  );
  // Missing: When can they retry?
}

Impact:

  • Poor user experience
  • Unnecessary retry attempts
  • User frustration

Proposed Solution:

typescript
if (recentAttempts >= 3) {
  const retryAfter = 3600; // 1 hour in seconds
  return NextResponse.json(
    { 
      error: `Too many attempts. Please try again in ${retryAfter / 60} minutes.`,
      retryAfter 
    },
    { 
      status: 429,
      headers: { 'Retry-After': retryAfter.toString() }
    }
  );
}

Decision: [ ] Required [ ] Not Required [ ] Needs Discussion


🟢 LOW Issues (2)

9. Generic Validation Error Messages

Multiple Files
Status: ⏳ Pending Review

Problem:

  • Validation errors use generic messages
  • Don't specify which field failed validation
  • Makes client-side debugging harder

Example:

typescript
return NextResponse.json(
  { error: 'Invalid request data' },
  { status: 400 }
);

Proposed Solution:

typescript
return NextResponse.json(
  { 
    error: 'Validation failed',
    fields: {
      email: 'Invalid email format',
      jobId: 'Job ID is required'
    }
  },
  { status: 400 }
);

Decision: [ ] Required [ ] Not Required [ ] Needs Discussion


10. Inconsistent Success Response Format

Multiple Files
Status: ⏳ Pending Review

Problem:

  • Some routes return { success: true, data: {...} }
  • Others return { result: {...} }
  • Others return data directly

Impact:

  • Client needs multiple response handlers
  • Inconsistent API design
  • Harder to maintain

Proposed Solution: Standardize on:

typescript
// Success
{ success: true, data: {...}, message?: string }

// Error
{ success: false, error: string, code?: string }

Decision: [ ] Required [ ] Not Required [ ] Needs Discussion


Summary Statistics

SeverityCountReviewedFixed
🔴 Critical220
🟠 High332
🟡 Medium311
🟢 Low200
Total1063

Fixed Issues

  1. Job Analysis Resume Fetch - Now fails fast with clear error message
  2. Google Drive Folder Cleanup - Both routes now clean up orphaned folders
  3. Usage Stats Error Logging - Now logs errors and warns users of partial data

Routes with Good Error Handling (Reference Examples)

  • /api/account/delete/route.ts - Comprehensive error handling with cleanup
  • /api/admin/delete-user/route.ts - Good validation and permission checks
  • /api/google-drive/disconnect/route.ts - Graceful degradation
  • /api/jobs/save/route.ts - Proper usage limit checking

Next Steps

  1. Review each issue with stakeholder
  2. Mark decisions as Required/Not Required
  3. Implement fixes in order of severity
  4. Update this document as fixes are completed

Built with VitePress