← back to Norma
app/api/scraper/petitions/route.ts
90 lines
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';
import { requireRole } from '@/lib/require-role';
/**
* GET /api/scraper/petitions
* Returns trending petitions from the database.
*
* Query params:
* ?platform=change_org — filter by platform (change_org, moveon, care2)
* &climbers_only=true — only return petitions marked as climbers
* &category=education — filter by category
* &limit=50 — max results (default 50, max 500)
*/
export async function GET(request: NextRequest) {
const auth = requireRole(request, 'admin', 'staff');
if (auth instanceof NextResponse) return auth;
try {
const { searchParams } = new URL(request.url);
const platform = searchParams.get('platform');
const climbersOnly = searchParams.get('climbers_only');
const category = searchParams.get('category');
const limitParam = searchParams.get('limit');
const limit = Math.min(Math.max(parseInt(limitParam || '50', 10) || 50, 1), 500);
const conditions: string[] = [];
const values: unknown[] = [];
let paramIndex = 1;
if (platform) {
conditions.push(`platform = $${paramIndex}`);
values.push(platform);
paramIndex++;
}
if (climbersOnly === 'true') {
conditions.push(`is_climber = true`);
}
if (category) {
conditions.push(`category = $${paramIndex}`);
values.push(category);
paramIndex++;
}
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
const result = await query(
`SELECT
*,
CASE
WHEN growth_rate > 0 AND signature_count > 0 THEN
ROUND(growth_rate * 100.0 / GREATEST(signature_count, 1), 2)
ELSE 0
END AS computed_growth_velocity,
EXTRACT(EPOCH FROM (NOW() - COALESCE(created_date, scraped_at))) / 86400 AS days_since_creation
FROM trending_petitions
${whereClause}
ORDER BY
is_climber DESC,
signature_count DESC NULLS LAST
LIMIT $${paramIndex}`,
[...values, limit],
);
// Summary stats
const statsResult = await query(
`SELECT
COUNT(*) AS total_petitions,
COUNT(*) FILTER (WHERE is_climber = true) AS climber_count,
COUNT(DISTINCT platform) AS platform_count,
AVG(signature_count) FILTER (WHERE signature_count > 0) AS avg_signatures,
MAX(signature_count) AS max_signatures,
AVG(growth_rate) FILTER (WHERE growth_rate > 0) AS avg_growth_rate
FROM trending_petitions`,
);
return NextResponse.json({
petitions: result.rows,
stats: statsResult.rows[0] ?? null,
count: result.rows.length,
});
} catch (err) {
console.error('[api/scraper/petitions] GET error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to fetch trending petitions' }, { status: 500 });
}
}