← back to Patty
app/api/signatures/route.ts
107 lines
import { NextRequest, NextResponse } from 'next/server';
import { query, getClient } from '@/lib/db';
import { verifyAuth } from '@/lib/auth';
export async function GET(request: NextRequest) {
const user = verifyAuth(request);
if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const url = new URL(request.url);
const petitionId = url.searchParams.get('petition_id');
if (!petitionId) {
return NextResponse.json({ error: 'petition_id query param is required' }, { status: 400 });
}
try {
const result = await query(
`SELECT * FROM signatures WHERE petition_id = $1 ORDER BY created_at DESC`,
[petitionId]
);
const countResult = await query(
`SELECT COUNT(*)::int as total FROM signatures WHERE petition_id = $1`,
[petitionId]
);
return NextResponse.json({
rows: result.rows,
count: countResult.rows[0]?.total || 0,
});
} catch (err) {
console.error('[api/signatures] GET error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to fetch signatures' }, { status: 500 });
}
}
export async function POST(request: NextRequest) {
const user = verifyAuth(request);
if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const client = await getClient();
try {
const body = await request.json();
const { petition_id, signer_email, signer_name, signer_zip, signer_comment, is_public, opted_in_email } = body;
if (!petition_id || !signer_email) {
return NextResponse.json({ error: 'petition_id and signer_email are required' }, { status: 400 });
}
// Get IP address
const forwarded = request.headers.get('x-forwarded-for');
const ip = forwarded ? forwarded.split(',')[0].trim() : 'unknown';
await client.query('BEGIN');
// Insert signature (UNIQUE constraint on petition_id + signer_email handles dedup)
const sigResult = await client.query(
`INSERT INTO signatures (petition_id, signer_email, signer_name, signer_zip, signer_comment, is_public, ip_address, opted_in_email)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
ON CONFLICT (petition_id, signer_email) DO NOTHING
RETURNING *`,
[
petition_id,
signer_email,
signer_name || null,
signer_zip || null,
signer_comment || null,
is_public !== false,
ip,
opted_in_email || false,
]
);
if (sigResult.rows.length === 0) {
await client.query('ROLLBACK');
return NextResponse.json({ error: 'This email has already signed this petition' }, { status: 409 });
}
// Update petition signature_count
await client.query(
`UPDATE petitions SET signature_count = signature_count + 1 WHERE id = $1`,
[petition_id]
);
// If opted in, add to email_subscribers
if (opted_in_email) {
await client.query(
`INSERT INTO email_subscribers (email, name, zip_code, source, petition_ids)
VALUES ($1, $2, $3, 'petition', ARRAY[$4::uuid])
ON CONFLICT DO NOTHING`,
[signer_email, signer_name || null, signer_zip || null, petition_id]
);
}
await client.query('COMMIT');
return NextResponse.json({ row: sigResult.rows[0] }, { status: 201 });
} catch (err) {
await client.query('ROLLBACK');
console.error('[api/signatures] POST error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to add signature' }, { status: 500 });
} finally {
client.release();
}
}