@@ -12,7 +12,8 @@ router.use(authenticateToken);
1212// Helper to separate sensitive configuration from response data
1313const sanitizePhoneNumber = ( pn : any ) => {
1414 if ( ! pn ) return null ;
15- const { configuration, provider, livekit_outbound_trunk_id, livekit_inbound_trunk_id, ...safeData } = pn ;
15+ // Keep inbound_agent_id and fallback_number, but hide internal LiveKit IDs and config
16+ const { configuration, provider, livekit_outbound_trunk_id, livekit_inbound_trunk_id, livekit_dispatch_rule_id, ...safeData } = pn ;
1617 return safeData ;
1718} ;
1819
@@ -327,6 +328,122 @@ router.put('/:id', async (req: Request, res: Response) => {
327328 }
328329} ) ;
329330
331+ // Configure inbound settings (assign agent, create inbound trunk + dispatch rule)
332+ router . put ( '/:id/inbound' , async ( req : Request , res : Response ) => {
333+ const { id } = req . params ;
334+ const { agentId, fallbackNumber } = req . body ;
335+ const userId = ( req as any ) . user . id ;
336+
337+ try {
338+ // Fetch the phone number
339+ const phoneResult = await pool . query (
340+ 'SELECT * FROM phone_numbers WHERE id = $1 AND user_id = $2' ,
341+ [ id , userId ]
342+ ) ;
343+
344+ if ( phoneResult . rows . length === 0 ) {
345+ return res . status ( 404 ) . json ( { error : 'Phone number not found' } ) ;
346+ }
347+
348+ const phoneNumber = phoneResult . rows [ 0 ] ;
349+ const livekitService = new LiveKitSipConfigService ( ) ;
350+
351+ // Determine agent name from environment
352+ const isProduction = process . env . NODE_ENV === 'production' ;
353+ const agentName = process . env . AGENT_NAME || ( isProduction ? 'aylin-agent' : 'aylin-dev-agent' ) ;
354+
355+ // If agentId is null/empty, remove inbound settings
356+ if ( ! agentId ) {
357+ // Clean up existing LiveKit resources
358+ if ( phoneNumber . livekit_dispatch_rule_id ) {
359+ await livekitService . deleteDispatchRule ( phoneNumber . livekit_dispatch_rule_id ) ;
360+ }
361+ if ( phoneNumber . livekit_inbound_trunk_id ) {
362+ await livekitService . deleteTrunk ( phoneNumber . livekit_inbound_trunk_id ) ;
363+ }
364+
365+ const result = await pool . query (
366+ `UPDATE phone_numbers
367+ SET inbound_agent_id = NULL,
368+ livekit_inbound_trunk_id = NULL,
369+ livekit_dispatch_rule_id = NULL,
370+ fallback_number = NULL
371+ WHERE id = $1 AND user_id = $2
372+ RETURNING *` ,
373+ [ id , userId ]
374+ ) ;
375+
376+ console . log ( `Inbound settings removed for phone number ${ phoneNumber . phone_number } ` ) ;
377+ return res . json ( sanitizePhoneNumber ( result . rows [ 0 ] ) ) ;
378+ }
379+
380+ // Verify agent exists
381+ const agentResult = await pool . query ( 'SELECT id FROM agents WHERE id = $1' , [ agentId ] ) ;
382+ if ( agentResult . rows . length === 0 ) {
383+ return res . status ( 404 ) . json ( { error : 'Agent not found' } ) ;
384+ }
385+
386+ // Clean up old inbound resources if they exist
387+ if ( phoneNumber . livekit_dispatch_rule_id ) {
388+ await livekitService . deleteDispatchRule ( phoneNumber . livekit_dispatch_rule_id ) ;
389+ }
390+ if ( phoneNumber . livekit_inbound_trunk_id ) {
391+ await livekitService . deleteTrunk ( phoneNumber . livekit_inbound_trunk_id ) ;
392+ }
393+
394+ // Create new inbound trunk
395+ console . log ( `Creating inbound trunk for ${ phoneNumber . phone_number } ...` ) ;
396+ const inboundTrunk = await livekitService . createInboundTrunk ( {
397+ phoneNumber : phoneNumber . phone_number ,
398+ name : `Inbound-${ phoneNumber . phone_number . replace ( / [ ^ \w ] / g, '' ) } ` ,
399+ } ) ;
400+
401+ // Create dispatch rule linking to the agent
402+ console . log ( `Creating dispatch rule for agent ${ agentId } ...` ) ;
403+ const dispatchRule = await livekitService . createInboundDispatchRule ( {
404+ inboundTrunkId : inboundTrunk . inboundTrunkId ,
405+ agentName,
406+ agentId,
407+ } ) ;
408+
409+ // Update database
410+ const result = await pool . query (
411+ `UPDATE phone_numbers
412+ SET inbound_agent_id = $1,
413+ livekit_inbound_trunk_id = $2,
414+ livekit_dispatch_rule_id = $3,
415+ fallback_number = $4
416+ WHERE id = $5 AND user_id = $6
417+ RETURNING *` ,
418+ [ agentId , inboundTrunk . inboundTrunkId , dispatchRule . dispatchRuleId , fallbackNumber || null , id , userId ]
419+ ) ;
420+
421+ console . log ( `Inbound settings configured for ${ phoneNumber . phone_number } ` ) ;
422+ console . log ( ` Inbound Trunk: ${ inboundTrunk . inboundTrunkId } ` ) ;
423+ console . log ( ` Dispatch Rule: ${ dispatchRule . dispatchRuleId } ` ) ;
424+ console . log ( ` Agent: ${ agentId } ` ) ;
425+
426+ const updatedPhone = result . rows [ 0 ] ;
427+ console . log ( 'Database result:' , {
428+ inbound_agent_id : updatedPhone . inbound_agent_id ,
429+ livekit_inbound_trunk_id : updatedPhone . livekit_inbound_trunk_id ,
430+ livekit_dispatch_rule_id : updatedPhone . livekit_dispatch_rule_id ,
431+ fallback_number : updatedPhone . fallback_number ,
432+ } ) ;
433+
434+ const sanitized = sanitizePhoneNumber ( updatedPhone ) ;
435+ console . log ( 'Sanitized response:' , {
436+ inbound_agent_id : sanitized . inbound_agent_id ,
437+ fallback_number : sanitized . fallback_number ,
438+ } ) ;
439+
440+ res . json ( sanitized ) ;
441+ } catch ( error : any ) {
442+ console . error ( 'Error configuring inbound settings:' , error ) ;
443+ res . status ( 500 ) . json ( { error : 'Failed to configure inbound settings' , message : error . message } ) ;
444+ }
445+ } ) ;
446+
330447router . delete ( '/:id' , async ( req : Request , res : Response ) => {
331448 const { id } = req . params ;
332449 const userId = ( req as any ) . user . id ;
@@ -346,23 +463,32 @@ router.delete('/:id', async (req: Request, res: Response) => {
346463 const config = phoneNumber . configuration ;
347464
348465 // Cleanup external resources if they exist
349- if ( config ) {
350- try {
351- // 1. Delete LiveKit trunk
352- if ( phoneNumber . livekit_outbound_trunk_id ) {
353- const livekitService = new LiveKitSipConfigService ( ) ;
354- await livekitService . deleteTrunk ( phoneNumber . livekit_outbound_trunk_id ) ;
355- }
466+ try {
467+ const livekitService = new LiveKitSipConfigService ( ) ;
356468
357- // 2. Delete Twilio trunk
358- if ( config . twilioTrunkSid && config . accountSid && config . authToken ) {
359- const twilioService = new TwilioSipTrunkService ( config . accountSid , config . authToken ) ;
360- await twilioService . deleteSipTrunk ( config . twilioTrunkSid ) ;
361- }
362- } catch ( cleanupError ) {
363- console . error ( 'Error cleaning up external resources:' , cleanupError ) ;
364- // Continue with deletion even if cleanup fails
469+ // 1. Delete inbound dispatch rule
470+ if ( phoneNumber . livekit_dispatch_rule_id ) {
471+ await livekitService . deleteDispatchRule ( phoneNumber . livekit_dispatch_rule_id ) ;
472+ }
473+
474+ // 2. Delete inbound trunk
475+ if ( phoneNumber . livekit_inbound_trunk_id ) {
476+ await livekitService . deleteTrunk ( phoneNumber . livekit_inbound_trunk_id ) ;
477+ }
478+
479+ // 3. Delete outbound trunk
480+ if ( phoneNumber . livekit_outbound_trunk_id ) {
481+ await livekitService . deleteTrunk ( phoneNumber . livekit_outbound_trunk_id ) ;
482+ }
483+
484+ // 4. Delete Twilio trunk
485+ if ( config && config . twilioTrunkSid && config . accountSid && config . authToken ) {
486+ const twilioService = new TwilioSipTrunkService ( config . accountSid , config . authToken ) ;
487+ await twilioService . deleteSipTrunk ( config . twilioTrunkSid ) ;
365488 }
489+ } catch ( cleanupError ) {
490+ console . error ( 'Error cleaning up external resources:' , cleanupError ) ;
491+ // Continue with deletion even if cleanup fails
366492 }
367493
368494 // Delete from database
0 commit comments