import { Elysia, t } from 'elysia'; import { db } from '../db'; import { clients, communications, events, interactions, clientNotes, emailTemplates, clientSegments } from '../db/schema'; import { and, eq, desc } from 'drizzle-orm'; import { logAudit, getRequestMeta } from '../services/audit'; export const exportRoutes = new Elysia({ prefix: '/export' }) // Full data export (JSON) .get('/json', async ({ headers, request }) => { const sessionToken = headers['cookie']?.match(/better-auth\.session_token=([^;]+)/)?.[1]; const bearerToken = headers['authorization']?.replace('Bearer ', ''); let userId: string | null = null; if (sessionToken) { const session = await db.query.sessions.findFirst({ where: (s, { eq, gt }) => and(eq(s.token, sessionToken), gt(s.expiresAt, new Date())), }); userId = session?.userId ?? null; } if (!userId && bearerToken) { const admin = await db.query.users.findFirst({ where: (u, { eq }) => eq(u.role, 'admin') }); userId = admin?.id ?? null; } if (!userId) return new Response('Unauthorized', { status: 401 }); const [ allClients, allEmails, allEvents, allInteractions, allNotes, allTemplates, allSegments, ] = await Promise.all([ db.select().from(clients).where(eq(clients.userId, userId)).orderBy(desc(clients.createdAt)), db.select().from(communications).where(eq(communications.userId, userId)).orderBy(desc(communications.createdAt)), db.select().from(events).where(eq(events.userId, userId)).orderBy(desc(events.date)), db.select().from(interactions).where(eq(interactions.userId, userId)).orderBy(desc(interactions.createdAt)), db.select().from(clientNotes).where(eq(clientNotes.userId, userId)).orderBy(desc(clientNotes.createdAt)), db.select().from(emailTemplates).where(eq(emailTemplates.userId, userId)).orderBy(desc(emailTemplates.createdAt)), db.select().from(clientSegments).where(eq(clientSegments.userId, userId)).orderBy(desc(clientSegments.createdAt)), ]); const meta = getRequestMeta(request); await logAudit({ userId, action: 'view', entityType: 'export' as any, entityId: 'full-json', details: { clients: allClients.length, emails: allEmails.length, events: allEvents.length, interactions: allInteractions.length, notes: allNotes.length, templates: allTemplates.length, segments: allSegments.length, }, ...meta, }); const exportData = { exportedAt: new Date().toISOString(), version: '1.0', summary: { clients: allClients.length, emails: allEmails.length, events: allEvents.length, interactions: allInteractions.length, notes: allNotes.length, templates: allTemplates.length, segments: allSegments.length, }, data: { clients: allClients, emails: allEmails, events: allEvents, interactions: allInteractions, notes: allNotes, templates: allTemplates, segments: allSegments, }, }; return new Response(JSON.stringify(exportData, null, 2), { headers: { 'Content-Type': 'application/json', 'Content-Disposition': `attachment; filename="network-app-export-${new Date().toISOString().split('T')[0]}.json"`, }, }); }) // CSV export for clients .get('/clients/csv', async ({ headers, request }) => { const sessionToken = headers['cookie']?.match(/better-auth\.session_token=([^;]+)/)?.[1]; const bearerToken = headers['authorization']?.replace('Bearer ', ''); let userId: string | null = null; if (sessionToken) { const session = await db.query.sessions.findFirst({ where: (s, { eq, gt }) => and(eq(s.token, sessionToken), gt(s.expiresAt, new Date())), }); userId = session?.userId ?? null; } if (!userId && bearerToken) { const admin = await db.query.users.findFirst({ where: (u, { eq }) => eq(u.role, 'admin') }); userId = admin?.id ?? null; } if (!userId) return new Response('Unauthorized', { status: 401 }); const allClients = await db.select().from(clients).where(eq(clients.userId, userId)).orderBy(desc(clients.createdAt)); const csvHeaders = [ 'First Name', 'Last Name', 'Email', 'Phone', 'Company', 'Role', 'Industry', 'Stage', 'Street', 'City', 'State', 'Zip', 'Birthday', 'Anniversary', 'Interests', 'Tags', 'Notes', 'Last Contacted', 'Created At', ]; const escCsv = (v: any) => { if (v == null) return ''; const s = String(v); if (s.includes(',') || s.includes('"') || s.includes('\n')) { return `"${s.replace(/"/g, '""')}"`; } return s; }; const rows = allClients.map(c => [ c.firstName, c.lastName, c.email, c.phone, c.company, c.role, c.industry, c.stage, c.street, c.city, c.state, c.zip, c.birthday?.toISOString().split('T')[0], c.anniversary?.toISOString().split('T')[0], ((c.interests as string[]) || []).join('; '), ((c.tags as string[]) || []).join('; '), c.notes, c.lastContactedAt?.toISOString(), c.createdAt.toISOString(), ].map(escCsv).join(',')); const csv = [csvHeaders.join(','), ...rows].join('\n'); const csvMeta = getRequestMeta(request); await logAudit({ userId, action: 'view', entityType: 'export' as any, entityId: 'clients-csv', details: { clientCount: allClients.length }, ...csvMeta, }); return new Response(csv, { headers: { 'Content-Type': 'text/csv', 'Content-Disposition': `attachment; filename="clients-export-${new Date().toISOString().split('T')[0]}.csv"`, }, }); }) // CSV export for interactions .get('/interactions/csv', async ({ headers, request }) => { const sessionToken = headers['cookie']?.match(/better-auth\.session_token=([^;]+)/)?.[1]; const bearerToken = headers['authorization']?.replace('Bearer ', ''); let userId: string | null = null; if (sessionToken) { const session = await db.query.sessions.findFirst({ where: (s, { eq, gt }) => and(eq(s.token, sessionToken), gt(s.expiresAt, new Date())), }); userId = session?.userId ?? null; } if (!userId && bearerToken) { const admin = await db.query.users.findFirst({ where: (u, { eq }) => eq(u.role, 'admin') }); userId = admin?.id ?? null; } if (!userId) return new Response('Unauthorized', { status: 401 }); const allInteractions = await db .select({ id: interactions.id, type: interactions.type, title: interactions.title, description: interactions.description, duration: interactions.duration, contactedAt: interactions.contactedAt, createdAt: interactions.createdAt, clientFirstName: clients.firstName, clientLastName: clients.lastName, clientEmail: clients.email, }) .from(interactions) .leftJoin(clients, eq(interactions.clientId, clients.id)) .where(eq(interactions.userId, userId)) .orderBy(desc(interactions.contactedAt)); const csvHeaders = ['Client Name', 'Client Email', 'Type', 'Title', 'Description', 'Duration (min)', 'Date', 'Created At']; const escCsv = (v: any) => { if (v == null) return ''; const s = String(v); if (s.includes(',') || s.includes('"') || s.includes('\n')) { return `"${s.replace(/"/g, '""')}"`; } return s; }; const rows = allInteractions.map(i => [ `${i.clientFirstName} ${i.clientLastName}`, i.clientEmail, i.type, i.title, i.description, i.duration, i.contactedAt.toISOString(), i.createdAt.toISOString(), ].map(escCsv).join(',')); const csv = [csvHeaders.join(','), ...rows].join('\n'); return new Response(csv, { headers: { 'Content-Type': 'text/csv', 'Content-Disposition': `attachment; filename="interactions-export-${new Date().toISOString().split('T')[0]}.csv"`, }, }); }) // Export summary/stats .get('/summary', async ({ headers }) => { const sessionToken = headers['cookie']?.match(/better-auth\.session_token=([^;]+)/)?.[1]; const bearerToken = headers['authorization']?.replace('Bearer ', ''); let userId: string | null = null; if (sessionToken) { const session = await db.query.sessions.findFirst({ where: (s, { eq, gt }) => and(eq(s.token, sessionToken), gt(s.expiresAt, new Date())), }); userId = session?.userId ?? null; } if (!userId && bearerToken) { const admin = await db.query.users.findFirst({ where: (u, { eq }) => eq(u.role, 'admin') }); userId = admin?.id ?? null; } if (!userId) return new Response('Unauthorized', { status: 401 }); const [clientCount, emailCount, eventCount, interactionCount, noteCount, templateCount, segmentCount] = await Promise.all([ db.select({ count: sql`count(*)` }).from(clients).where(eq(clients.userId, userId)), db.select({ count: sql`count(*)` }).from(communications).where(eq(communications.userId, userId)), db.select({ count: sql`count(*)` }).from(events).where(eq(events.userId, userId)), db.select({ count: sql`count(*)` }).from(interactions).where(eq(interactions.userId, userId)), db.select({ count: sql`count(*)` }).from(clientNotes).where(eq(clientNotes.userId, userId)), db.select({ count: sql`count(*)` }).from(emailTemplates).where(eq(emailTemplates.userId, userId)), db.select({ count: sql`count(*)` }).from(clientSegments).where(eq(clientSegments.userId, userId)), ]); return { clients: Number(clientCount[0]?.count || 0), emails: Number(emailCount[0]?.count || 0), events: Number(eventCount[0]?.count || 0), interactions: Number(interactionCount[0]?.count || 0), notes: Number(noteCount[0]?.count || 0), templates: Number(templateCount[0]?.count || 0), segments: Number(segmentCount[0]?.count || 0), exportFormats: ['json', 'clients-csv', 'interactions-csv'], }; }); // Need sql import import { sql } from 'drizzle-orm';