← back to Stayclaim
src/app/api/submit/route.ts
56 lines
import { NextResponse } from 'next/server';
import { z } from 'zod';
import { pool } from '@/lib/db';
import { rateLimit } from '@/lib/rate-limit';
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
const Body = z.object({
address_line1: z.string().min(3).max(255),
city: z.string().min(2).max(120),
state: z.string().min(2).max(2).transform(s => s.toUpperCase()).pipe(z.string().regex(/^[A-Z]{2}$/)),
postal_code: z.string().max(12).regex(/^[A-Z0-9 \-]{3,12}$/i).optional().or(z.literal('')),
notes: z.string().max(2000).optional().or(z.literal('')),
contact_email: z.string().email().max(254),
});
export async function POST(req: Request) {
const rl = await rateLimit('submit');
if (!rl.ok) {
return NextResponse.json(
{ error: 'rate_limited' },
{ status: 429, headers: { 'Retry-After': String(rl.retryAfter ?? 600) } },
);
}
let parsed;
try {
parsed = Body.parse(await req.json());
} catch (e) {
const detail = e instanceof z.ZodError ? e.flatten() : undefined;
return NextResponse.json({ error: 'invalid body', detail }, { status: 400 });
}
try {
const r = await pool.query(
`INSERT INTO address_submission
(address_line1, city, state, postal_code, notes, contact_email)
VALUES ($1,$2,$3,$4,$5,$6)
RETURNING id, status, submitted_at`,
[
parsed.address_line1,
parsed.city,
parsed.state,
parsed.postal_code || null,
parsed.notes || null,
parsed.contact_email,
]
);
const { id, status, submitted_at } = r.rows[0];
return NextResponse.json({ id, status, submitted_at }, { status: 201 });
} catch (err) {
console.error('[submit] db insert failed', err);
return NextResponse.json({ error: 'internal' }, { status: 500 });
}
}