diff --git a/src/app/api/public/interests/route.ts b/src/app/api/public/interests/route.ts index b096e02..ae2ae42 100644 --- a/src/app/api/public/interests/route.ts +++ b/src/app/api/public/interests/route.ts @@ -3,10 +3,13 @@ import { and, eq } from 'drizzle-orm'; import { db } from '@/lib/db'; import { interests } from '@/lib/db/schema/interests'; -import { clients, clientContacts } from '@/lib/db/schema/clients'; +import { clients, clientContacts, clientAddresses } from '@/lib/db/schema/clients'; +import { berths } from '@/lib/db/schema/berths'; +import { ports } from '@/lib/db/schema/ports'; import { createAuditLog } from '@/lib/audit'; import { errorResponse, RateLimitError } from '@/lib/errors'; import { publicInterestSchema } from '@/lib/validators/interests'; +import { sendInquiryNotifications } from '@/lib/services/inquiry-notifications.service'; // ─── Simple in-memory rate limiter ─────────────────────────────────────────── // Max 5 requests per hour per IP @@ -47,90 +50,68 @@ export async function POST(req: NextRequest) { return NextResponse.json({ error: 'Port context required' }, { status: 400 }); } + // Resolve the full name + const fullName = + data.firstName && data.lastName + ? `${data.firstName} ${data.lastName}` + : (data.fullName ?? 'Unknown'); + + const firstName = data.firstName ?? fullName.split(/\s+/)[0] ?? 'Valued Guest'; + + // Resolve berth by mooring number (if provided) + let berthId: string | null = null; + let resolvedMooringNumber: string | null = data.mooringNumber ?? null; + + if (data.mooringNumber) { + const berth = await db.query.berths.findFirst({ + where: and(eq(berths.mooringNumber, data.mooringNumber), eq(berths.portId, portId)), + }); + if (berth) { + berthId = berth.id; + resolvedMooringNumber = berth.mooringNumber; + } + } + // Find or create client by email let clientId: string; const existingContact = await db.query.clientContacts.findFirst({ - where: and( - eq(clientContacts.channel, 'email'), - eq(clientContacts.value, data.email), - ), + where: and(eq(clientContacts.channel, 'email'), eq(clientContacts.value, data.email)), }); if (existingContact) { - // Find the client associated with this contact const existingClient = await db.query.clients.findFirst({ where: eq(clients.id, existingContact.clientId), }); if (existingClient && existingClient.portId === portId) { clientId = existingClient.id; - } else { - // Create new client for this port - const [newClient] = await db - .insert(clients) - .values({ - portId, - fullName: data.fullName, - companyName: data.companyName, - yachtName: data.yachtName, - yachtLengthFt: data.yachtLengthFt != null ? String(data.yachtLengthFt) : undefined, - yachtWidthFt: data.yachtWidthFt != null ? String(data.yachtWidthFt) : undefined, - yachtDraftFt: data.yachtDraftFt != null ? String(data.yachtDraftFt) : undefined, - berthSizeDesired: data.preferredBerthSize, - source: 'website', - }) - .returning(); - clientId = newClient!.id; - - await db.insert(clientContacts).values({ - clientId, - channel: 'email', - value: data.email, - isPrimary: true, - }); - - if (data.phone) { - await db.insert(clientContacts).values({ - clientId, - channel: 'phone', - value: data.phone, - isPrimary: false, - }); + // Update preferred contact method if provided + if (data.preferredContactMethod) { + await db + .update(clients) + .set({ preferredContactMethod: data.preferredContactMethod }) + .where(eq(clients.id, clientId)); } + } else { + clientId = await createNewClient(portId, fullName, data); } } else { - // Create brand-new client - const [newClient] = await db - .insert(clients) - .values({ - portId, - fullName: data.fullName, - companyName: data.companyName, - yachtName: data.yachtName, - yachtLengthFt: data.yachtLengthFt != null ? String(data.yachtLengthFt) : undefined, - yachtWidthFt: data.yachtWidthFt != null ? String(data.yachtWidthFt) : undefined, - yachtDraftFt: data.yachtDraftFt != null ? String(data.yachtDraftFt) : undefined, - berthSizeDesired: data.preferredBerthSize, - source: 'website', - }) - .returning(); - clientId = newClient!.id; + clientId = await createNewClient(portId, fullName, data); + } - await db.insert(clientContacts).values({ + // Store address if provided + if (data.address && Object.values(data.address).some(Boolean)) { + await db.insert(clientAddresses).values({ clientId, - channel: 'email', - value: data.email, + portId, + label: 'Primary', + streetAddress: data.address.street ?? null, + city: data.address.city ?? null, + stateProvince: data.address.stateProvince ?? null, + postalCode: data.address.postalCode ?? null, + country: data.address.country ?? null, isPrimary: true, }); - - if (data.phone) { - await db.insert(clientContacts).values({ - clientId, - channel: 'phone', - value: data.phone, - isPrimary: false, - }); - } } // Create the interest @@ -139,6 +120,7 @@ export async function POST(req: NextRequest) { .values({ portId, clientId, + berthId, source: 'website', pipelineStage: 'open', notes: data.notes, @@ -151,12 +133,29 @@ export async function POST(req: NextRequest) { action: 'create', entityType: 'interest', entityId: interest!.id, - newValue: { clientId, source: 'website', pipelineStage: 'open' }, + newValue: { clientId, source: 'website', pipelineStage: 'open', berthId }, metadata: { type: 'public_registration', ip }, ipAddress: ip, userAgent: req.headers.get('user-agent') ?? 'unknown', }); + // Fire notifications asynchronously (non-blocking) + const port = await db.query.ports.findFirst({ + where: eq(ports.id, portId), + columns: { slug: true }, + }); + + void sendInquiryNotifications({ + portId, + portSlug: port?.slug ?? portId, + interestId: interest!.id, + clientFullName: fullName, + clientEmail: data.email, + clientPhone: data.phone, + mooringNumber: resolvedMooringNumber, + firstName, + }); + return NextResponse.json( { data: { id: interest!.id, message: 'Interest registered successfully' } }, { status: 201 }, @@ -165,3 +164,52 @@ export async function POST(req: NextRequest) { return errorResponse(error); } } + +async function createNewClient( + portId: string, + fullName: string, + data: { + email: string; + phone: string; + companyName?: string; + yachtName?: string; + yachtLengthFt?: number; + yachtWidthFt?: number; + yachtDraftFt?: number; + preferredBerthSize?: string; + preferredContactMethod?: string; + }, +): Promise { + const [newClient] = await db + .insert(clients) + .values({ + portId, + fullName, + companyName: data.companyName, + yachtName: data.yachtName, + yachtLengthFt: data.yachtLengthFt != null ? String(data.yachtLengthFt) : undefined, + yachtWidthFt: data.yachtWidthFt != null ? String(data.yachtWidthFt) : undefined, + yachtDraftFt: data.yachtDraftFt != null ? String(data.yachtDraftFt) : undefined, + berthSizeDesired: data.preferredBerthSize, + preferredContactMethod: data.preferredContactMethod, + source: 'website', + }) + .returning(); + const clientId = newClient!.id; + + await db.insert(clientContacts).values({ + clientId, + channel: 'email', + value: data.email, + isPrimary: true, + }); + + await db.insert(clientContacts).values({ + clientId, + channel: 'phone', + value: data.phone, + isPrimary: false, + }); + + return clientId; +}