import { Controller, Get, Query, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { PlatformGraphqlService } from '../platform/platform-graphql.service'; @Controller('api/search') export class SearchController { private readonly logger = new Logger(SearchController.name); private readonly platformApiKey: string; constructor( private readonly platform: PlatformGraphqlService, private readonly config: ConfigService, ) { this.platformApiKey = config.get('platform.apiKey') ?? ''; } @Get() async search(@Query('q') query?: string) { if (!query || query.length < 2) { return { leads: [], patients: [], appointments: [] }; } const authHeader = this.platformApiKey ? `Bearer ${this.platformApiKey}` : ''; if (!authHeader) { return { leads: [], patients: [], appointments: [] }; } this.logger.log(`Search: "${query}"`); // Fetch all three in parallel, filter client-side for flexible matching try { const [leadsResult, patientsResult, appointmentsResult] = await Promise.all([ this.platform.queryWithAuth( `{ leads(first: 50) { edges { node { id name contactName { firstName lastName } contactPhone { primaryPhoneNumber } source status interestedService } } } }`, undefined, authHeader, ).catch(() => ({ leads: { edges: [] } })), this.platform.queryWithAuth( `{ patients(first: 50) { edges { node { id name fullName { firstName lastName } phones { primaryPhoneNumber } gender dateOfBirth } } } }`, undefined, authHeader, ).catch(() => ({ patients: { edges: [] } })), this.platform.queryWithAuth( `{ appointments(first: 50, orderBy: [{ scheduledAt: DescNullsLast }]) { edges { node { id scheduledAt doctorName department status patientId } } } }`, undefined, authHeader, ).catch(() => ({ appointments: { edges: [] } })), ]); const q = query.toLowerCase(); const leads = (leadsResult.leads?.edges ?? []) .map((e: any) => e.node) .filter((l: any) => { const name = `${l.contactName?.firstName ?? ''} ${l.contactName?.lastName ?? ''}`.toLowerCase(); const phone = l.contactPhone?.primaryPhoneNumber ?? ''; return name.includes(q) || phone.includes(q) || (l.name ?? '').toLowerCase().includes(q); }) .slice(0, 5); const patients = (patientsResult.patients?.edges ?? []) .map((e: any) => e.node) .filter((p: any) => { const name = `${p.fullName?.firstName ?? ''} ${p.fullName?.lastName ?? ''}`.toLowerCase(); const phone = p.phones?.primaryPhoneNumber ?? ''; return name.includes(q) || phone.includes(q) || (p.name ?? '').toLowerCase().includes(q); }) .slice(0, 5); const appointments = (appointmentsResult.appointments?.edges ?? []) .map((e: any) => e.node) .filter((a: any) => { const doctor = (a.doctorName ?? '').toLowerCase(); const dept = (a.department ?? '').toLowerCase(); return doctor.includes(q) || dept.includes(q); }) .slice(0, 5); return { leads, patients, appointments }; } catch (err: any) { this.logger.error(`Search failed: ${err.message}`); return { leads: [], patients: [], appointments: [] }; } } }