← back to Norma

app/api/action-network/route.ts

86 lines

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

const AN_BASE = 'https://actionnetwork.org/api/v2';

/**
 * GET /api/action-network?action=petitions|signatures|events
 * Fetch Action Network data. Syncs to local DB on petition fetch.
 */
export async function GET(request: NextRequest) {
  const auth = requireRole(request, 'admin', 'staff');
  if (auth instanceof NextResponse) return auth;

  const apiKey = process.env.ACTION_NETWORK_API_KEY;
  if (!apiKey) {
    // Return local DB data if API key not set — graceful degradation
    const { searchParams } = new URL(request.url);
    const action = searchParams.get('action') || 'petitions';
    if (action === 'petitions') {
      const result = await query(
        `SELECT * FROM action_network_petitions ORDER BY created_at DESC LIMIT 50`
      );
      return NextResponse.json({ petitions: result.rows, source: 'db_fallback', count: result.rows.length });
    }
    return NextResponse.json({ error: 'ACTION_NETWORK_API_KEY not configured' }, { status: 503 });
  }

  const { searchParams } = new URL(request.url);
  const action = searchParams.get('action') || 'petitions';
  const page = parseInt(searchParams.get('page') || '1', 10);

  try {
    if (action === 'petitions') {
      const res = await fetch(`${AN_BASE}/petitions?page=${page}`, {
        headers: { 'OSDI-API-Token': apiKey },
      });
      if (!res.ok) throw new Error(`Action Network returned ${res.status}`);
      const data = await res.json();
      const petitions = data._embedded?.['osdi:petitions'] || [];

      // Upsert into local DB
      for (const p of petitions) {
        await query(
          `INSERT INTO action_network_petitions
            (an_id, title, description, petition_text, target, total_signatures, created_at, modified_at)
           VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
           ON CONFLICT (an_id) DO UPDATE SET
             total_signatures = EXCLUDED.total_signatures,
             modified_at = EXCLUDED.modified_at`,
          [
            p.identifiers?.[0]?.replace('action_network:', ''),
            p.title || p.name,
            p.description || null,
            p.petition_text || null,
            JSON.stringify(p.target || []),
            p.total_signatures || 0,
            p.created_date || null,
            p.modified_date || null,
          ]
        ).catch(() => {}); // schema may differ, ignore
      }

      // Update last_synced
      await query(
        `UPDATE connected_services SET last_synced_at = NOW(), metadata = metadata || $1::jsonb WHERE service_id = 'action_network'`,
        [JSON.stringify({ petition_count: petitions.length })]
      );

      return NextResponse.json({ petitions, source: 'api', count: petitions.length });
    }

    if (action === 'events') {
      const res = await fetch(`${AN_BASE}/events?page=${page}`, {
        headers: { 'OSDI-API-Token': apiKey },
      });
      const data = await res.json();
      return NextResponse.json({ events: data._embedded?.['osdi:events'] || [] });
    }

    return NextResponse.json({ error: `Unknown action: ${action}` }, { status: 400 });
  } catch (err) {
    return NextResponse.json({ error: (err as Error).message }, { status: 500 });
  }
}