← back to Norma
app/api/sessions/route.ts
153 lines
import { NextRequest, NextResponse } from 'next/server';
import { query, getClient } from '@/lib/db';
import { requireRole } from '@/lib/require-role';
import { auditLog } from '@/lib/audit';
import { getOrgId } from '@/lib/orgId';
/**
* GET /api/sessions
* List sessions ordered by session_date DESC, with nested drafts.
* Supports pagination: ?limit=50&offset=0
*/
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;
let limit = parseInt(searchParams.get('limit') || '50', 10);
if (isNaN(limit) || limit < 1) limit = 50;
if (limit > 200) limit = 200;
let offset = parseInt(searchParams.get('offset') || '0', 10);
if (isNaN(offset) || offset < 0) offset = 0;
const orgWhere = orgId ? 'WHERE s.org_id = $3' : '';
const orgCountWhere = orgId ? 'WHERE org_id = $1' : '';
const countParams = orgId ? [orgId] : [];
const countResult = await query(
`SELECT COUNT(*)::int AS total FROM sessions ${orgCountWhere}`,
countParams
);
const total = countResult.rows[0]?.total ?? 0;
const queryParams: unknown[] = [limit, offset];
if (orgId) queryParams.push(orgId);
const sessionsResult = await query(
`SELECT s.*,
COALESCE(
json_agg(
json_build_object(
'id', d.id,
'lane', d.lane,
'lane_label', d.lane_label,
'subject', d.subject,
'body_html', COALESCE(d.body_html, ''),
'body_text', COALESCE(d.body_text, ''),
'email_type', d.email_type,
'voice_mode', d.voice_mode,
'status', d.status,
'current_version', d.current_version,
'created_at', d.created_at,
'updated_at', d.updated_at
) ORDER BY d.lane
) FILTER (WHERE d.id IS NOT NULL),
'[]'::json
) AS drafts
FROM sessions s
LEFT JOIN drafts d ON d.session_id = s.id
${orgWhere}
GROUP BY s.id
ORDER BY s.session_date DESC
LIMIT $1 OFFSET $2`,
queryParams
);
return NextResponse.json({ sessions: sessionsResult.rows, total, limit, offset });
} catch (err) {
console.error('[api/sessions] GET error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to fetch sessions' }, { status: 500 });
}
}
/**
* POST /api/sessions
* Create a new "Morning Batch" session with 3 empty lane drafts (A/B/C).
*/
export async function POST(request: NextRequest) {
const auth = requireRole(request, 'admin', 'staff');
if (auth instanceof NextResponse) return auth;
const orgId = auth.role === 'admin' ? getOrgId(request) : auth.orgId;
const client = await getClient();
try {
await client.query('BEGIN');
// Create the session
const today = new Date().toISOString().split('T')[0];
const title = `Morning Batch - ${new Date().toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
})}`;
const sessionResult = await client.query(
`INSERT INTO sessions (title, session_date, status, created_by, org_id)
VALUES ($1, $2, 'draft', $3, $4)
RETURNING *`,
[title, today, auth.username, orgId || null]
);
const session = sessionResult.rows[0];
// Create 3 lane drafts
const lanes = [
{ lane: 'A', lane_label: 'Action', email_type: 'action' },
{ lane: 'B', lane_label: 'Policy', email_type: 'policy' },
{ lane: 'C', lane_label: 'Resources', email_type: 'resources' },
];
const drafts = [];
for (const l of lanes) {
const draftResult = await client.query(
`INSERT INTO drafts (session_id, lane, lane_label, subject, body_html, body_text, email_type, status, created_by, org_id)
VALUES ($1, $2, $3, $4, $5, $6, $7, 'draft', $8, $9)
RETURNING *`,
[
session.id,
l.lane,
l.lane_label,
`[${l.lane_label}] `,
'',
'',
l.email_type, auth.username,
orgId || null,
]
);
drafts.push(draftResult.rows[0]);
}
await client.query('COMMIT');
const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || undefined;
await auditLog('session.created', 'session', session.id, { title, lanes: lanes.map((l) => l.lane) }, ip);
return NextResponse.json(
{ session: { ...session, drafts } },
{ status: 201 }
);
} catch (err) {
await client.query('ROLLBACK');
console.error('[api/sessions] POST error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to create session' }, { status: 500 });
} finally {
client.release();
}
}