Files
network-app-api/src/routes/activity.ts

153 lines
4.8 KiB
TypeScript

import { Elysia, t } from 'elysia';
import { db } from '../db';
import { clients, events, communications, interactions } from '../db/schema';
import { eq, and, desc } from 'drizzle-orm';
import type { User } from '../lib/auth';
export interface ActivityItem {
id: string;
type: 'email_sent' | 'email_drafted' | 'event_created' | 'client_contacted' | 'client_created' | 'client_updated' | 'interaction';
title: string;
description?: string;
date: string;
metadata?: Record<string, any>;
}
export const activityRoutes = new Elysia({ prefix: '/clients' })
// Get activity timeline for a client
.get('/:id/activity', async ({ params, user }: { params: { id: string }; user: User }) => {
// Verify client belongs to user
const [client] = await db.select()
.from(clients)
.where(and(eq(clients.id, params.id), eq(clients.userId, user.id)))
.limit(1);
if (!client) {
throw new Error('Client not found');
}
const activities: ActivityItem[] = [];
// Client creation
activities.push({
id: `created-${client.id}`,
type: 'client_created',
title: 'Client added to network',
date: client.createdAt.toISOString(),
});
// Client updated (if different from created)
if (client.updatedAt.getTime() - client.createdAt.getTime() > 60000) {
activities.push({
id: `updated-${client.id}`,
type: 'client_updated',
title: 'Client profile updated',
date: client.updatedAt.toISOString(),
});
}
// Last contacted
if (client.lastContactedAt) {
activities.push({
id: `contacted-${client.id}`,
type: 'client_contacted',
title: 'Marked as contacted',
date: client.lastContactedAt.toISOString(),
});
}
// Communications (emails)
const comms = await db.select()
.from(communications)
.where(and(
eq(communications.clientId, params.id),
eq(communications.userId, user.id),
))
.orderBy(desc(communications.createdAt));
for (const comm of comms) {
if (comm.status === 'sent' && comm.sentAt) {
activities.push({
id: `email-sent-${comm.id}`,
type: 'email_sent',
title: `Email sent: ${comm.subject || 'No subject'}`,
description: comm.content.substring(0, 150) + (comm.content.length > 150 ? '...' : ''),
date: comm.sentAt.toISOString(),
metadata: { emailId: comm.id, aiGenerated: comm.aiGenerated },
});
}
// Also show drafts
if (comm.status === 'draft') {
activities.push({
id: `email-draft-${comm.id}`,
type: 'email_drafted',
title: `Email drafted: ${comm.subject || 'No subject'}`,
description: comm.content.substring(0, 150) + (comm.content.length > 150 ? '...' : ''),
date: comm.createdAt.toISOString(),
metadata: { emailId: comm.id, aiGenerated: comm.aiGenerated },
});
}
}
// Events
const clientEvents = await db.select()
.from(events)
.where(and(
eq(events.clientId, params.id),
eq(events.userId, user.id),
))
.orderBy(desc(events.createdAt));
for (const event of clientEvents) {
activities.push({
id: `event-${event.id}`,
type: 'event_created',
title: `Event: ${event.title}`,
description: `${event.type}${event.recurring ? ' (recurring)' : ''}`,
date: event.createdAt.toISOString(),
metadata: { eventId: event.id, eventType: event.type, eventDate: event.date.toISOString() },
});
}
// Interactions
const clientInteractions = await db.select()
.from(interactions)
.where(and(
eq(interactions.clientId, params.id),
eq(interactions.userId, user.id),
))
.orderBy(desc(interactions.contactedAt));
for (const interaction of clientInteractions) {
const typeLabels: Record<string, string> = {
call: '📞 Phone Call',
meeting: '🤝 Meeting',
email: '✉️ Email',
note: '📝 Note',
other: '📌 Interaction',
};
activities.push({
id: `interaction-${interaction.id}`,
type: 'interaction',
title: `${typeLabels[interaction.type] || typeLabels.other}: ${interaction.title}`,
description: interaction.description || undefined,
date: interaction.contactedAt.toISOString(),
metadata: {
interactionId: interaction.id,
interactionType: interaction.type,
duration: interaction.duration,
},
});
}
// Sort by date descending
activities.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
return activities;
}, {
params: t.Object({
id: t.String({ format: 'uuid' }),
}),
});