import { NextRequest, NextResponse } from 'next/server'; import { validateEmail, isDisposableEmail } from '@/lib/email-validation'; import { checkRateLimit, getClientIp } from '@/lib/rate-limit'; const SIGNUP_LIST_KEY = 'signups:emails'; const isDev = !process.env.KV_REST_API_URL; // In-memory fallback for development const devSignups = new Map(); export async function POST(request: NextRequest) { try { const body = await request.json(); const { email } = body; if (!email || typeof email !== 'string') { return NextResponse.json( { error: "That email doesn't look right." }, { status: 400 } ); } const validation = validateEmail(email); if (!validation.success) { return NextResponse.json( { error: "That email doesn't look right." }, { status: 400 } ); } const sanitizedEmail = validation.email!; if (isDisposableEmail(sanitizedEmail)) { return NextResponse.json( { error: "That email doesn't look right." }, { status: 400 } ); } const clientIp = getClientIp(request); const rateLimitResult = await checkRateLimit(clientIp); if (!rateLimitResult.allowed) { const retrySeconds = Math.ceil((rateLimitResult.resetAt - Date.now()) / 1000); return NextResponse.json( { error: 'Too fast. Try again in a minute.', resetAt: rateLimitResult.resetAt, retryAfter: retrySeconds, }, { status: 429, headers: { 'Retry-After': retrySeconds.toString(), }, } ); } if (isDev) { console.warn('[DEV MODE] Using in-memory signup storage - data will not persist'); if (devSignups.has(sanitizedEmail)) { return NextResponse.json( { error: "Already got you. I'll keep you posted." }, { status: 409 } ); } const signupData = { email: sanitizedEmail, timestamp: new Date().toISOString(), ip: clientIp, }; devSignups.set(sanitizedEmail, signupData); return NextResponse.json( { success: true, message: 'Thanks. That means a lot.', }, { status: 200, headers: { 'X-RateLimit-Remaining': rateLimitResult.remaining.toString(), 'X-RateLimit-Reset': rateLimitResult.resetAt.toString(), }, } ); } // Production: Use Vercel KV const { kv } = await import('@vercel/kv'); const existingSignup = await kv.hget(SIGNUP_LIST_KEY, sanitizedEmail); if (existingSignup) { return NextResponse.json( { error: "Already got you. I'll keep you posted." }, { status: 409 } ); } const signupData = { email: sanitizedEmail, timestamp: new Date().toISOString(), ip: clientIp, }; await kv.hset(SIGNUP_LIST_KEY, { [sanitizedEmail]: JSON.stringify(signupData), }); return NextResponse.json( { success: true, message: 'Thanks. That means a lot.', }, { 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: 'Something broke. 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 } ); }