← back to Norma
app/api/pulse/tracked/route.ts
182 lines
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';
import { requireRole } from '@/lib/require-role';
import type { AuthSession } from '@/lib/auth';
/**
* GET /api/pulse/tracked
* Returns tracked petitions for the authenticated user.
* Requires any authenticated role (admin, staff, pulse).
*
* Query params:
* ?page=1 — page number (default 1)
* ?limit=20 — results per page (default 20, max 100)
*/
export async function GET(request: NextRequest) {
const result = requireRole(request, 'admin', 'staff', 'pulse');
if (result instanceof NextResponse) return result;
const session = result as AuthSession;
try {
const { searchParams } = new URL(request.url);
let page = parseInt(searchParams.get('page') || '1', 10);
if (isNaN(page) || page < 1) page = 1;
let limit = parseInt(searchParams.get('limit') || '20', 10);
if (isNaN(limit) || limit < 1) limit = 20;
if (limit > 100) limit = 100;
const offset = (page - 1) * limit;
// Count total tracked
const countRes = await query(
`SELECT COUNT(*)::int AS total
FROM pulse_tracked_petitions
WHERE username = $1`,
[session.username],
);
const total = countRes.rows[0]?.total ?? 0;
// Fetch tracked petitions with petition details
const dataRes = await query(
`SELECT
pt.id AS tracking_id,
pt.created_at AS tracked_at,
p.id AS petition_id,
p.title,
p.description,
p.body,
p.category,
p.status,
p.target,
p.signature_count,
p.signature_goal,
p.is_featured,
p.tags,
p.updated_at AS petition_updated_at
FROM pulse_tracked_petitions pt
JOIN petitions p ON p.id = pt.petition_id
WHERE pt.username = $1
ORDER BY pt.created_at DESC
LIMIT $2 OFFSET $3`,
[session.username, limit, offset],
);
return NextResponse.json({
tracked: dataRes.rows,
total,
page,
limit,
totalPages: Math.ceil(total / limit),
});
} catch (err) {
console.error('[api/pulse/tracked] GET error:', (err as Error).message);
return NextResponse.json(
{ error: 'Failed to fetch tracked petitions' },
{ status: 500 },
);
}
}
/**
* POST /api/pulse/tracked
* Track a petition for the authenticated user.
* Body: { petition_id: string (UUID) }
*/
export async function POST(request: NextRequest) {
const result = requireRole(request, 'admin', 'staff', 'pulse');
if (result instanceof NextResponse) return result;
const session = result as AuthSession;
try {
const body = await request.json();
const petitionId = body.petition_id;
if (!petitionId || typeof petitionId !== 'string') {
return NextResponse.json(
{ error: 'petition_id is required' },
{ status: 400 },
);
}
// Verify petition exists
const petCheck = await query(
`SELECT id FROM petitions WHERE id = $1`,
[petitionId],
);
if (petCheck.rows.length === 0) {
return NextResponse.json(
{ error: 'Petition not found' },
{ status: 404 },
);
}
// Insert tracking (ON CONFLICT ignore duplicate)
const insertRes = await query(
`INSERT INTO pulse_tracked_petitions (username, petition_id)
VALUES ($1, $2)
ON CONFLICT (username, petition_id) DO NOTHING
RETURNING id, created_at`,
[session.username, petitionId],
);
if (insertRes.rows.length === 0) {
return NextResponse.json({ message: 'Already tracking this petition' });
}
return NextResponse.json({
tracked: insertRes.rows[0],
message: 'Petition tracked successfully',
}, { status: 201 });
} catch (err) {
console.error('[api/pulse/tracked] POST error:', (err as Error).message);
return NextResponse.json(
{ error: 'Failed to track petition' },
{ status: 500 },
);
}
}
/**
* DELETE /api/pulse/tracked
* Untrack a petition for the authenticated user.
* Body: { petition_id: string (UUID) }
*/
export async function DELETE(request: NextRequest) {
const result = requireRole(request, 'admin', 'staff', 'pulse');
if (result instanceof NextResponse) return result;
const session = result as AuthSession;
try {
const body = await request.json();
const petitionId = body.petition_id;
if (!petitionId || typeof petitionId !== 'string') {
return NextResponse.json(
{ error: 'petition_id is required' },
{ status: 400 },
);
}
const deleteRes = await query(
`DELETE FROM pulse_tracked_petitions
WHERE username = $1 AND petition_id = $2
RETURNING id`,
[session.username, petitionId],
);
if (deleteRes.rows.length === 0) {
return NextResponse.json(
{ error: 'Tracking not found' },
{ status: 404 },
);
}
return NextResponse.json({ message: 'Petition untracked successfully' });
} catch (err) {
console.error('[api/pulse/tracked] DELETE error:', (err as Error).message);
return NextResponse.json(
{ error: 'Failed to untrack petition' },
{ status: 500 },
);
}
}