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