- Build complete Next.js CRM for commercial real estate - Add authentication with JWT sessions and role-based access - Add GoHighLevel API integration for contacts, conversations, opportunities - Add AI-powered Control Center with tool calling - Add Setup page with onboarding checklist (/setup) - Add sidebar navigation with Setup menu item - Fix type errors in onboarding API, GHL services, and control center tools - Add Prisma schema with SQLite for local development - Add UI components with clay morphism design system Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
91 lines
2.4 KiB
TypeScript
91 lines
2.4 KiB
TypeScript
import { prisma } from '@/lib/db';
|
|
import { broadcaster } from '@/lib/realtime/broadcaster';
|
|
import { REALTIME_EVENTS } from '@/lib/realtime/events';
|
|
|
|
export interface GHLWebhookPayload {
|
|
type: string;
|
|
locationId: string;
|
|
data: any;
|
|
}
|
|
|
|
export async function handleGHLWebhook(payload: GHLWebhookPayload): Promise<{ success: boolean }> {
|
|
const { type, locationId, data } = payload;
|
|
|
|
console.log('[GHL Webhook] Received:', type, locationId);
|
|
|
|
// Find user by locationId
|
|
const user = await prisma.user.findFirst({
|
|
where: { ghlLocationId: locationId },
|
|
});
|
|
|
|
if (!user) {
|
|
console.log('[GHL Webhook] No user found for location:', locationId);
|
|
return { success: false };
|
|
}
|
|
|
|
// Process different webhook types
|
|
switch (type) {
|
|
case 'ContactCreate':
|
|
await broadcaster.broadcastToUser(user.id, REALTIME_EVENTS.CONTACT_CREATED, {
|
|
contact: data,
|
|
});
|
|
break;
|
|
|
|
case 'ContactUpdate':
|
|
await broadcaster.broadcastToUser(user.id, REALTIME_EVENTS.CONTACT_UPDATED, {
|
|
contact: data,
|
|
});
|
|
break;
|
|
|
|
case 'ContactDelete':
|
|
await broadcaster.broadcastToUser(user.id, REALTIME_EVENTS.CONTACT_DELETED, {
|
|
contactId: data.id,
|
|
});
|
|
break;
|
|
|
|
case 'InboundMessage':
|
|
case 'OutboundMessage':
|
|
await broadcaster.broadcastToUser(user.id, REALTIME_EVENTS.MESSAGE_RECEIVED, {
|
|
message: data,
|
|
direction: type === 'InboundMessage' ? 'inbound' : 'outbound',
|
|
});
|
|
break;
|
|
|
|
case 'OpportunityCreate':
|
|
await broadcaster.broadcastToUser(user.id, REALTIME_EVENTS.OPPORTUNITY_CREATED, {
|
|
opportunity: data,
|
|
});
|
|
break;
|
|
|
|
case 'OpportunityUpdate':
|
|
await broadcaster.broadcastToUser(user.id, REALTIME_EVENTS.OPPORTUNITY_UPDATED, {
|
|
opportunity: data,
|
|
});
|
|
break;
|
|
|
|
case 'OpportunityStageUpdate':
|
|
await broadcaster.broadcastToUser(user.id, REALTIME_EVENTS.OPPORTUNITY_STAGE_CHANGED, {
|
|
opportunity: data,
|
|
previousStage: data.previousStage,
|
|
newStage: data.newStage,
|
|
});
|
|
break;
|
|
|
|
default:
|
|
console.log('[GHL Webhook] Unhandled type:', type);
|
|
}
|
|
|
|
// Log webhook receipt
|
|
await prisma.auditLog.create({
|
|
data: {
|
|
userId: user.id,
|
|
action: 'WEBHOOK_RECEIVED',
|
|
resource: 'webhook',
|
|
resourceId: type,
|
|
details: { type, dataKeys: Object.keys(data) },
|
|
},
|
|
});
|
|
|
|
return { success: true };
|
|
}
|