import { NextResponse } from 'next/server'; import { validateEmail, isDisposableEmail } from '@gridpilot/identity/domain/value-objects/EmailAddress'; import { checkRateLimit, getClientIp } from '@/lib/rate-limit'; const SIGNUP_DEV_STORE = new Map(); const SIGNUP_KV_HASH_KEY = 'signups:emails'; const isDev = !process.env.KV_REST_API_URL; function jsonError(status: number, message: string, extra: Record = {}) { return NextResponse.json( { error: message, ...extra, }, { status }, ); } export async function POST(request: Request) { let body: unknown; try { body = await request.json(); } catch { return jsonError(400, 'Invalid request body'); } const email = typeof body === 'object' && body !== null && 'email' in body ? (body as { email: unknown }).email : undefined; if (typeof email !== 'string' || !email.trim()) { return jsonError(400, 'Invalid email address'); } const validation = validateEmail(email); if (!validation.success) { return jsonError(400, validation.error || 'Invalid email address'); } const normalizedEmail = validation.email; if (isDisposableEmail(normalizedEmail)) { return jsonError(400, 'Disposable email addresses are not allowed'); } const ip = getClientIp(request); try { const rateResult = await checkRateLimit(ip); if (!rateResult.allowed) { const retryAfterSeconds = Math.max(0, Math.round((rateResult.resetAt - Date.now()) / 1000)); return jsonError(429, 'Too many signups, please try again later.', { retryAfter: retryAfterSeconds, }); } } catch { return jsonError(503, 'Temporarily unable to accept signups.'); } try { if (isDev) { const existing = SIGNUP_DEV_STORE.get(normalizedEmail); if (existing) { return jsonError(409, 'You are already on the list.'); } SIGNUP_DEV_STORE.set(normalizedEmail, { email: normalizedEmail, createdAt: Date.now(), ip, }); } else { const { kv } = await import('@vercel/kv'); const existing = await kv.hget<{ email: string; createdAt: number; ip: string }>( SIGNUP_KV_HASH_KEY, normalizedEmail, ); if (existing) { return jsonError(409, 'You are already on the list.'); } await kv.hset(SIGNUP_KV_HASH_KEY, { [normalizedEmail]: { email: normalizedEmail, createdAt: Date.now(), ip, }, }); } } catch (error) { console.error('Signup storage error:', error); return jsonError(503, 'Temporarily unable to accept signups.'); } return NextResponse.json( { ok: true, message: 'You are on the grid! We will be in touch soon.', }, { status: 201, }, ); }