auto-claude: subtask-2-2 - Create API route for contact form submission
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { supabaseRateLimiter } from '@/utils/rateLimitSupabase';
|
||||
|
||||
interface ContactFormData {
|
||||
name: string;
|
||||
email: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract client IP address from Next.js request headers
|
||||
* Handles Vercel's forwarding headers and fallback scenarios
|
||||
*/
|
||||
function getClientIp(request: NextRequest): string {
|
||||
// Check Vercel's forwarded IP header first
|
||||
const forwardedFor = request.headers.get('x-forwarded-for');
|
||||
if (forwardedFor) {
|
||||
// x-forwarded-for may contain multiple IPs, take the first one
|
||||
return forwardedFor.split(',')[0].trim();
|
||||
}
|
||||
|
||||
// Check for real IP header
|
||||
const realIp = request.headers.get('x-real-ip');
|
||||
if (realIp) {
|
||||
return realIp;
|
||||
}
|
||||
|
||||
// Fallback to a default identifier for development
|
||||
return 'unknown-ip';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate contact form data
|
||||
*/
|
||||
function validateFormData(data: unknown): data is ContactFormData {
|
||||
if (!data || typeof data !== 'object') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const formData = data as Record<string, unknown>;
|
||||
|
||||
return (
|
||||
typeof formData.name === 'string' &&
|
||||
formData.name.trim().length > 0 &&
|
||||
typeof formData.email === 'string' &&
|
||||
/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email) &&
|
||||
typeof formData.message === 'string' &&
|
||||
formData.message.trim().length > 0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/contact
|
||||
* Handle contact form submissions with rate limiting
|
||||
*/
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
// Extract client IP for rate limiting
|
||||
const clientIp = getClientIp(request);
|
||||
|
||||
// Check rate limit
|
||||
const isRateLimited = await supabaseRateLimiter.isRateLimited(clientIp);
|
||||
|
||||
if (isRateLimited) {
|
||||
const timeToReset = await supabaseRateLimiter.getTimeToReset(clientIp);
|
||||
const retryAfterSeconds = Math.ceil(timeToReset / 1000);
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: 'Too many requests. Please try again later.',
|
||||
retryAfter: retryAfterSeconds,
|
||||
},
|
||||
{
|
||||
status: 429,
|
||||
headers: {
|
||||
'X-RateLimit-Remaining': '0',
|
||||
'Retry-After': retryAfterSeconds.toString(),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Parse and validate request body
|
||||
const body = await request.json();
|
||||
|
||||
if (!validateFormData(body)) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid form data. Please check all fields.' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const formData = body as ContactFormData;
|
||||
|
||||
// Get remaining attempts for response headers
|
||||
const remainingAttempts = await supabaseRateLimiter.getRemainingAttempts(clientIp);
|
||||
|
||||
// TODO: In a real implementation, you would:
|
||||
// 1. Send email via a service like SendGrid, Resend, or AWS SES
|
||||
// 2. Store the message in a database
|
||||
// 3. Send a confirmation email to the user
|
||||
// For now, we'll just simulate success
|
||||
|
||||
// Simulate processing delay
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: true,
|
||||
message: 'Your message has been received. We will get back to you soon!',
|
||||
},
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
'X-RateLimit-Remaining': remainingAttempts.toString(),
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error processing contact form:', error);
|
||||
|
||||
return NextResponse.json(
|
||||
{ error: 'An unexpected error occurred. Please try again later.' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user