import { NextRequest, NextResponse } from 'next/server'; import { kv } from '@vercel/kv'; import { validateEmail, isDisposableEmail } from '@/lib/email-validation'; import { checkRateLimit, getClientIp } from '@/lib/rate-limit'; /** * Email signup storage key */ const SIGNUP_LIST_KEY = 'signups:emails'; /** * POST /api/signup * Handle email signup submissions */ export async function POST(request: NextRequest) { try { // Parse request body const body = await request.json(); const { email } = body; if (!email || typeof email !== 'string') { return NextResponse.json( { error: 'Email is required' }, { status: 400 } ); } // Validate email format const validation = validateEmail(email); if (!validation.success) { return NextResponse.json( { error: validation.error }, { status: 400 } ); } const sanitizedEmail = validation.email!; // Check for disposable email if (isDisposableEmail(sanitizedEmail)) { return NextResponse.json( { error: 'Disposable email addresses are not allowed' }, { status: 400 } ); } // Rate limiting const clientIp = getClientIp(request); const rateLimitResult = await checkRateLimit(clientIp); if (!rateLimitResult.allowed) { return NextResponse.json( { error: 'Too many requests. Please try again later.', resetAt: rateLimitResult.resetAt, }, { status: 429, headers: { 'Retry-After': Math.ceil((rateLimitResult.resetAt - Date.now()) / 1000).toString(), }, } ); } // Check if email already exists const existingSignup = await kv.hget(SIGNUP_LIST_KEY, sanitizedEmail); if (existingSignup) { return NextResponse.json( { error: 'This email is already registered' }, { status: 409 } ); } // Store email with timestamp const signupData = { email: sanitizedEmail, timestamp: new Date().toISOString(), ip: clientIp, }; await kv.hset(SIGNUP_LIST_KEY, { [sanitizedEmail]: JSON.stringify(signupData), }); // Return success response return NextResponse.json( { success: true, message: 'Successfully added to waitlist', }, { status: 200, headers: { 'X-RateLimit-Remaining': rateLimitResult.remaining.toString(), 'X-RateLimit-Reset': rateLimitResult.resetAt.toString(), }, } ); } catch (error) { console.error('Signup error:', error); return NextResponse.json( { error: 'An error occurred. Please try again.' }, { status: 500 } ); } } /** * GET /api/signup * Return 405 Method Not Allowed */ export async function GET() { return NextResponse.json( { error: 'Method not allowed' }, { status: 405 } ); }