← back to Norma
app/api/drafts/[id]/export/route.ts
111 lines
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';
import { requireRole } from '@/lib/require-role';
import { auditLog } from '@/lib/audit';
import { getOrgId } from '@/lib/orgId';
import { buildEml, buildHtmlDocument, htmlToText, buildJsonExport } from '@/lib/export';
type RouteContext = { params: Promise<{ id: string }> };
/**
* GET /api/drafts/[id]/export
* Export a draft in the specified format.
* Query param: ?format=eml|html|txt|json
*/
export async function GET(request: NextRequest, context: RouteContext) {
const auth = requireRole(request, 'admin', 'staff');
if (auth instanceof NextResponse) return auth;
const orgId = auth.role === 'admin' ? getOrgId(request) : auth.orgId;
if (!orgId) {
return NextResponse.json({ error: 'Missing org context' }, { status: 400 });
}
const { id } = await context.params;
const { searchParams } = new URL(request.url);
const format = searchParams.get('format') || 'html';
if (!['eml', 'html', 'txt', 'json'].includes(format)) {
return NextResponse.json(
{ error: 'Invalid format. Supported: eml, html, txt, json' },
{ status: 400 }
);
}
try {
const draftResult = await query(`SELECT * FROM drafts WHERE id = $1 AND org_id = $2`, [id, orgId]);
if (draftResult.rowCount === 0) {
return NextResponse.json({ error: 'Draft not found' }, { status: 404 });
}
const draft = draftResult.rows[0];
const safeSubject = (draft.subject || 'draft').replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 60);
const ip = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || undefined;
await auditLog('draft.exported', 'draft', id, { format }, ip);
switch (format) {
case 'eml': {
const emlContent = buildEml({
subject: draft.subject,
from_name: draft.from_name,
from_email: draft.from_email,
body_html: draft.body_html,
body_text: draft.body_text || htmlToText(draft.body_html),
});
return new NextResponse(emlContent, {
status: 200,
headers: {
'Content-Type': 'message/rfc822',
'Content-Disposition': `attachment; filename="${safeSubject}.eml"`,
},
});
}
case 'html': {
const htmlDoc = buildHtmlDocument(draft.subject, draft.body_html);
return new NextResponse(htmlDoc, {
status: 200,
headers: {
'Content-Type': 'text/html; charset=utf-8',
'Content-Disposition': `attachment; filename="${safeSubject}.html"`,
},
});
}
case 'txt': {
const textContent = draft.body_text || htmlToText(draft.body_html);
const fullText = `Subject: ${draft.subject}\nFrom: ${draft.from_name} <${draft.from_email}>\n\n${textContent}`;
return new NextResponse(fullText, {
status: 200,
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Content-Disposition': `attachment; filename="${safeSubject}.txt"`,
},
});
}
case 'json': {
const jsonContent = buildJsonExport(draft);
return new NextResponse(jsonContent, {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'Content-Disposition': `attachment; filename="${safeSubject}.json"`,
},
});
}
default:
return NextResponse.json({ error: 'Unsupported format' }, { status: 400 });
}
} catch (err) {
console.error('[api/drafts/[id]/export] GET error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to export draft' }, { status: 500 });
}
}