← back to Norma
app/api/archives/route.ts
136 lines
import { NextRequest, NextResponse } from 'next/server';
import { query } from '@/lib/db';
import { requireRole } from '@/lib/require-role';
/**
* GET /api/archives
* List historical emails, workshops, newsletters, campaigns.
* Optional filters: ?type=email|workshop|newsletter|campaign|template
* ?search=keyword
* ?tags=tag1,tag2
* ?featured=true
*/
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 archiveType = searchParams.get('type');
const search = searchParams.get('search');
const tags = searchParams.get('tags');
const featured = searchParams.get('featured');
const conditions: string[] = [];
const values: unknown[] = [];
let idx = 1;
if (archiveType) {
conditions.push(`archive_type = $${idx}`);
values.push(archiveType);
idx++;
}
if (search) {
conditions.push(`(title ILIKE $${idx} OR subject ILIKE $${idx} OR content_text ILIKE $${idx} OR campaign_name ILIKE $${idx})`);
values.push(`%${search}%`);
idx++;
}
if (tags) {
const tagArray = tags.split(',').map(t => t.trim()).filter(Boolean);
conditions.push(`tags && $${idx}`);
values.push(tagArray);
idx++;
}
if (featured === 'true') {
conditions.push(`is_featured = true`);
}
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 where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
const countResult = await query(
`SELECT COUNT(*)::int AS total FROM historical_archives ${where}`,
values
);
const result = await query(
`SELECT id, archive_type, title, subject, description, sender, date_sent,
campaign_name, tags, source, drive_url, metrics, is_featured, usage_count,
created_at, updated_at
FROM historical_archives ${where}
ORDER BY is_featured DESC, date_sent DESC NULLS LAST
LIMIT $${idx} OFFSET $${idx + 1}`,
[...values, limit, offset]
);
return NextResponse.json({
archives: result.rows,
total: countResult.rows[0]?.total || 0,
limit,
offset,
});
} catch (err) {
console.error('[api/archives] GET error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to fetch archives' }, { status: 500 });
}
}
/**
* POST /api/archives
* Create a new historical archive entry.
*/
export async function POST(request: NextRequest) {
const auth = requireRole(request, 'admin', 'staff');
if (auth instanceof NextResponse) return auth;
try {
const body = await request.json();
if (!body.archive_type || !body.title) {
return NextResponse.json({ error: 'archive_type and title are required' }, { status: 400 });
}
const result = await query(
`INSERT INTO historical_archives
(archive_type, title, subject, description, content_html, content_text,
sender, recipients, date_sent, campaign_name, tags, source, drive_file_id,
drive_url, attachments, metrics, is_featured)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17)
RETURNING *`,
[
body.archive_type,
body.title,
body.subject || null,
body.description || null,
body.content_html || null,
body.content_text || null,
body.sender || null,
body.recipients || null,
body.date_sent || null,
body.campaign_name || null,
body.tags || [],
body.source || 'drive',
body.drive_file_id || null,
body.drive_url || null,
body.attachments ? JSON.stringify(body.attachments) : '[]',
body.metrics ? JSON.stringify(body.metrics) : '{}',
body.is_featured || false,
]
);
return NextResponse.json({ archive: result.rows[0] }, { status: 201 });
} catch (err) {
console.error('[api/archives] POST error:', (err as Error).message);
return NextResponse.json({ error: 'Failed to create archive' }, { status: 500 });
}
}