← back to Norma
app/api/intelligence/activity/route.ts
78 lines
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';
import { requireRole } from '@/lib/require-role';
import { getOrgId } from '@/lib/orgId';
/**
* GET /api/intelligence/activity
* Query params:
* ?limit= — rows to return (default 50, max 200)
* ?type= — comma-separated activity_type values
* e.g. "news_trigger,journalist_mention"
* Headers:
* x-org-id — scope results to a specific org
* Returns: { activities: [...] }
* Ordered by created_at DESC.
*/
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 orgId = auth.role === 'admin' ? getOrgId(request) : auth.orgId;
const typeParam = searchParams.get('type');
let limit = parseInt(searchParams.get('limit') || '50', 10);
if (isNaN(limit) || limit < 1) limit = 50;
if (limit > 200) limit = 200;
// Parse comma-separated activity types; discard empties
const activityTypes = typeParam
? typeParam.split(',').map((t) => t.trim()).filter(Boolean)
: [];
const conditions: string[] = [];
const values: unknown[] = [];
let paramIndex = 1;
if (orgId) {
conditions.push(`org_id = $${paramIndex}`);
values.push(orgId);
paramIndex++;
}
if (activityTypes.length > 0) {
// Use ANY($N::text[]) so a single parameterised binding handles N types
conditions.push(`activity_type = ANY($${paramIndex}::text[])`);
values.push(activityTypes);
paramIndex++;
}
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
const result = await query(
`SELECT
org_id,
activity_type,
entity_type,
entity_id,
entity_name,
summary,
delta,
relevance_score,
created_at
FROM org_activity
${whereClause}
ORDER BY created_at DESC
LIMIT $${paramIndex}`,
[...values, limit],
);
return NextResponse.json({ activities: result.rows });
} catch (err) {
console.error('[api/intelligence/activity] GET error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to fetch activity' }, { status: 500 });
}
}