All files / app/api/testimonials/requests route.ts

94.64% Statements 106/112
89.65% Branches 26/29
100% Functions 2/2
94.64% Lines 106/112

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 1501x     1x   7x 7x 7x   6x 6x 6x   7x 1x 1x     5x 5x 5x 5x 5x   7x 1x 1x     4x 4x 4x 4x 4x   7x 1x 1x 1x   3x 7x 1x 1x 1x 7x   10x 10x 10x 9x   9x 9x 9x   10x 1x 1x   8x 8x   10x 1x 1x     7x 7x 7x 7x 7x   10x 1x 1x     6x 6x 6x 6x 6x   10x     10x 1x 1x 1x 1x 1x     5x   10x         5x     5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x   10x 1x 1x 1x     3x 3x 3x 3x 3x 10x           3x   3x 3x 3x 3x 3x 10x 2x 2x 2x 10x  
import { NextRequest, NextResponse } from 'next/server';
import { createClient } from '@/lib/supabase/server';
import { getAdminClient } from '@/lib/supabase/admin';
import { createHash } from 'crypto';
 
export async function GET(_request: NextRequest) {
  try {
    const supabase = await createClient();
 
    const {
      data: { user },
    } = await supabase.auth.getUser();
 
    if (!user) {
      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
    }
 
    // Get user's profile
    const { data: profile } = await supabase
      .from('profiles')
      .select('id')
      .eq('user_id', user.id)
      .single();
 
    if (!profile) {
      return NextResponse.json({ error: 'Profile not found' }, { status: 404 });
    }
 
    // Get testimonial requests
    const { data: requests, error } = await supabase
      .from('testimonial_requests')
      .select('*')
      .eq('profile_id', profile.id)
      .order('created_at', { ascending: false });
 
    if (error) {
      console.error('Error fetching requests:', error);
      return NextResponse.json({ error: 'Failed to fetch requests' }, { status: 500 });
    }
 
    return NextResponse.json({ requests });
  } catch (error) {
    console.error('Testimonial requests GET error:', error);
    return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
  }
}
 
export async function POST(request: NextRequest) {
  try {
    const supabase = await createClient();
    const adminClient = getAdminClient();
 
    const {
      data: { user },
    } = await supabase.auth.getUser();
 
    if (!user) {
      return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
    }
 
    const body = await request.json();
    const { recipient_email, recipient_name, relationship_context, custom_message } = body;
 
    if (!recipient_email) {
      return NextResponse.json({ error: 'Recipient email is required' }, { status: 400 });
    }
 
    // Get user's profile
    const { data: profile } = await supabase
      .from('profiles')
      .select('id, subscription_tier, display_name')
      .eq('user_id', user.id)
      .single();
 
    if (!profile) {
      return NextResponse.json({ error: 'Profile not found' }, { status: 404 });
    }
 
    // Check rate limit (5 requests per day per profile)
    const { data: canRequest, error: rateLimitError } = await adminClient.rpc('check_rate_limit', {
      p_key_type: 'profile_requests',
      p_key_value: profile.id,
      p_limit: 5,
    });
 
    if (rateLimitError) {
      console.error('Error checking rate limit:', rateLimitError);
      // Continue anyway - don't block the request if rate limit check fails
    } else if (canRequest === false) {
      return NextResponse.json(
        { error: 'Rate limit exceeded. Please try again tomorrow.' },
        { status: 429 }
      );
    }
 
    // Generate token
    const { data: token, error: tokenError } = await adminClient.rpc('generate_request_token');
 
    if (tokenError || !token) {
      console.error('Error generating token:', tokenError);
      return NextResponse.json({ error: 'Failed to generate request token' }, { status: 500 });
    }
 
    const tokenHash = createHash('sha256').update(token).digest('hex');
 
    // Create request
    const { data: requestData, error } = await adminClient
      .from('testimonial_requests')
      .insert({
        profile_id: profile.id,
        token,
        token_hash: tokenHash,
        recipient_email,
        recipient_name,
        relationship_context,
        custom_message,
      })
      .select()
      .single();
 
    if (error) {
      console.error('Error creating request:', error);
      return NextResponse.json({ error: 'Failed to create request' }, { status: 500 });
    }
 
    // Increment rate limit (don't fail if this errors)
    try {
      await adminClient.rpc('increment_rate_limit', {
        p_key_type: 'profile_requests',
        p_key_value: profile.id,
      });
    } catch (rateLimitErr) {
      console.error('Error incrementing rate limit:', rateLimitErr);
    }
 
    // TODO: Send email to recipient with the token link
    // For now, return the request with the token for testing
    const submitUrl = `${process.env.NEXT_PUBLIC_APP_URL}/testimonial/submit/${token}`;
 
    return NextResponse.json({
      request: requestData,
      submitUrl,
      message: 'Request created successfully',
    });
  } catch (error) {
    console.error('Testimonial request POST error:', error);
    return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
  }
}