← back to Norma

app/api/agents/congress-by-zip/route.ts

91 lines

import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';
import { requireRole } from '@/lib/require-role';

/**
 * GET /api/agents/congress-by-zip?zip=90210
 * Look up congress members and their staffers by ZIP code.
 */
export async function GET(request: NextRequest) {
  const auth = requireRole(request, 'admin', 'staff');
  if (auth instanceof NextResponse) return auth;

  try {
    const zip = request.nextUrl.searchParams.get('zip');
    if (!zip || zip.length < 5) {
      return NextResponse.json({ error: 'Valid ZIP code required' }, { status: 400 });
    }

    // Find politicians whose district covers this ZIP
    const membersResult = await query(
      `SELECT DISTINCT
        p.id, p.name, p.title, p.party, p.state, p.district,
        p.congressional_district, p.phone, p.email, p.website,
        p.social_media, p.photo_url, p.position_student_debt,
        dz.overlap_pct
       FROM politicians p
       JOIN congressional_districts cd ON cd.id = p.district_id
       JOIN district_zips dz ON dz.district_id = cd.id
       WHERE dz.zip = $1
       ORDER BY dz.overlap_pct DESC NULLS LAST`,
      [zip],
    );

    // Determine state from ZIP — use matched members or fall back to zip_profiles/zip_crosswalk
    let memberState = membersResult.rows[0]?.state || null;
    if (!memberState) {
      const stateResult = await query(
        `SELECT state FROM zip_profiles WHERE zip = $1
         UNION ALL
         SELECT state_abbr FROM zip_crosswalk WHERE zip = $1
         LIMIT 1`,
        [zip],
      );
      memberState = stateResult.rows[0]?.state || stateResult.rows[0]?.state_abbr || null;
    }

    // Get state-level senators (always, even if no district member found)
    let senators: any[] = [];
    if (memberState) {
      const existingIds = membersResult.rows.map((r: any) => r.id);
      const senResult = await query(
        `SELECT id, name, title, party, state, district, congressional_district,
                phone, email, website, social_media, photo_url, position_student_debt
         FROM politicians
         WHERE state = $1 AND (title ILIKE '%senator%' OR title ILIKE '%sen.%')
         AND office_level = 'federal'
         ${existingIds.length ? `AND id NOT IN (SELECT unnest($2::uuid[]))` : ''}`,
        existingIds.length ? [memberState, existingIds] : [memberState],
      );
      senators = senResult.rows;
    }

    const allMembers = [...membersResult.rows, ...senators];

    // Get staffers for all members
    let staffers: any[] = [];
    if (allMembers.length > 0) {
      const memberIds = allMembers.map((m: any) => m.id);
      const staffResult = await query(
        `SELECT id, politician_id, full_name, title, role_category,
                email, phone, handles_education, handles_finance, is_key_contact
         FROM congressional_staffers
         WHERE politician_id = ANY($1::uuid[])
         ORDER BY is_key_contact DESC, handles_education DESC, full_name ASC`,
        [memberIds],
      );
      staffers = staffResult.rows;
    }

    return NextResponse.json({
      zip,
      members: allMembers,
      staffers,
      total: allMembers.length,
    });
  } catch (err) {
    console.error('[agents/congress-by-zip] Error:', (err as Error).message);
    return NextResponse.json({ error: 'Failed to look up congress members' }, { status: 500 });
  }
}