← 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();
  }
}