@@ -161,6 +161,36 @@ func createPhoneNumberClient(ctx context.Context, cmd *cli.Command) (*lksdk.Phon
161161 return lksdk .NewPhoneNumberClient (project .URL , project .APIKey , project .APISecret , withDefaultClientOpts (project )... ), nil
162162}
163163
164+ // getPhoneNumberToDispatchRulesMap fetches all dispatch rules and maps phone number IDs to their associated dispatch rule IDs
165+ // Returns a map where key is phone number ID and value is a slice of dispatch rule IDs
166+ func getPhoneNumberToDispatchRulesMap (ctx context.Context , cmd * cli.Command ) (map [string ][]string , error ) {
167+ _ , err := requireProject (ctx , cmd )
168+ if err != nil {
169+ return nil , fmt .Errorf ("failed to get project: %w" , err )
170+ }
171+
172+ sipClient := lksdk .NewSIPClient (project .URL , project .APIKey , project .APISecret , withDefaultClientOpts (project )... )
173+
174+ // List all dispatch rules
175+ resp , err := sipClient .ListSIPDispatchRule (ctx , & livekit.ListSIPDispatchRuleRequest {})
176+ if err != nil {
177+ return nil , fmt .Errorf ("failed to list dispatch rules: %w" , err )
178+ }
179+
180+ // Build map: phone number ID -> []dispatch rule IDs
181+ phoneNumberToRules := make (map [string ][]string )
182+ for _ , rule := range resp .Items {
183+ for _ , trunkID := range rule .TrunkIds {
184+ // Check if trunkID is a phone number ID (starts with PN_PPN_)
185+ if strings .HasPrefix (trunkID , "PN_PPN_" ) {
186+ phoneNumberToRules [trunkID ] = append (phoneNumberToRules [trunkID ], rule .SipDispatchRuleId )
187+ }
188+ }
189+ }
190+
191+ return phoneNumberToRules , nil
192+ }
193+
164194// appendPhoneNumberToDispatchRule appends a phone number ID to the trunk_ids of a dispatch rule
165195func appendPhoneNumberToDispatchRule (ctx context.Context , cmd * cli.Command , dispatchRuleID , phoneNumberID string ) error {
166196 _ , err := requireProject (ctx , cmd )
@@ -181,10 +211,13 @@ func appendPhoneNumberToDispatchRule(ctx context.Context, cmd *cli.Command, disp
181211 currentRule := rules [0 ]
182212
183213 // Check if phone number ID is already in trunk_ids
184- for _ , trunkID := range currentRule .TrunkIds {
185- if trunkID == phoneNumberID {
186- // Already in the list, no need to update
187- return nil
214+ // Handle nil or empty trunk_ids explicitly
215+ if currentRule .TrunkIds != nil && len (currentRule .TrunkIds ) > 0 {
216+ for _ , trunkID := range currentRule .TrunkIds {
217+ if trunkID == phoneNumberID {
218+ // Already in the list, no need to update
219+ return nil
220+ }
188221 }
189222 }
190223
@@ -279,11 +312,38 @@ func purchasePhoneNumbers(ctx context.Context, cmd *cli.Command) error {
279312 PhoneNumbers : phoneNumbers ,
280313 }
281314
282- resp , err := client .PurchasePhoneNumber (ctx , req )
283- if err != nil {
284- return err
315+ // Call purchase and get dispatch rules in parallel
316+ type purchaseResult struct {
317+ resp * livekit.PurchasePhoneNumberResponse
318+ err error
319+ }
320+ type dispatchRulesResult struct {
321+ rules map [string ][]string
322+ err error
285323 }
286324
325+ purchaseChan := make (chan purchaseResult , 1 )
326+ dispatchRulesChan := make (chan dispatchRulesResult , 1 )
327+
328+ // Purchase phone numbers
329+ go func () {
330+ resp , err := client .PurchasePhoneNumber (ctx , req )
331+ purchaseChan <- purchaseResult {resp : resp , err : err }
332+ }()
333+
334+ // Get dispatch rules mapping in parallel
335+ go func () {
336+ rules , err := getPhoneNumberToDispatchRulesMap (ctx , cmd )
337+ dispatchRulesChan <- dispatchRulesResult {rules : rules , err : err }
338+ }()
339+
340+ // Wait for purchase to complete
341+ purchaseRes := <- purchaseChan
342+ if purchaseRes .err != nil {
343+ return purchaseRes .err
344+ }
345+ resp := purchaseRes .resp
346+
287347 // If dispatch rule ID was provided, append each purchased phone number ID to the dispatch rule's trunk_ids
288348 dispatchRuleAdded := make (map [string ]bool )
289349 if dispatchRuleID != "" {
@@ -297,6 +357,26 @@ func purchasePhoneNumbers(ctx context.Context, cmd *cli.Command) error {
297357 }
298358 }
299359
360+ // Wait for dispatch rules (ignore errors, we'll just not show them)
361+ dispatchRulesRes := <- dispatchRulesChan
362+ phoneNumberToRules := dispatchRulesRes .rules
363+ if dispatchRulesRes .err != nil {
364+ // Log but don't fail
365+ if cmd .Bool ("verbose" ) {
366+ fmt .Fprintf (cmd .ErrWriter , "Warning: failed to get dispatch rules: %v\n " , dispatchRulesRes .err )
367+ }
368+ phoneNumberToRules = make (map [string ][]string )
369+ }
370+
371+ // Update the mapping with newly added dispatch rules
372+ if dispatchRuleID != "" {
373+ for _ , phoneNumber := range resp .PhoneNumbers {
374+ if dispatchRuleAdded [phoneNumber .Id ] {
375+ phoneNumberToRules [phoneNumber .Id ] = append (phoneNumberToRules [phoneNumber .Id ], dispatchRuleID )
376+ }
377+ }
378+ }
379+
300380 if cmd .Bool ("json" ) {
301381 util .PrintJSON (resp )
302382 return nil
@@ -305,8 +385,9 @@ func purchasePhoneNumbers(ctx context.Context, cmd *cli.Command) error {
305385 fmt .Printf ("Successfully purchased %d phone numbers:\n " , len (resp .PhoneNumbers ))
306386 for _ , phoneNumber := range resp .PhoneNumbers {
307387 ruleInfo := ""
308- if dispatchRuleAdded [phoneNumber .Id ] && dispatchRuleID != "" {
309- ruleInfo = fmt .Sprintf (" (SIP Dispatch Rule: %s)" , dispatchRuleID )
388+ rules := phoneNumberToRules [phoneNumber .Id ]
389+ if len (rules ) > 0 {
390+ ruleInfo = fmt .Sprintf (" (SIP Dispatch Rules: %s)" , strings .Join (rules , ", " ))
310391 } else if phoneNumber .SipDispatchRuleId != "" {
311392 ruleInfo = fmt .Sprintf (" (SIP Dispatch Rule: %s)" , phoneNumber .SipDispatchRuleId )
312393 }
@@ -342,9 +423,47 @@ func listPhoneNumbers(ctx context.Context, cmd *cli.Command) error {
342423 req .SipDispatchRuleId = & val
343424 }
344425
345- resp , err := client .ListPhoneNumbers (ctx , req )
346- if err != nil {
347- return err
426+ // Call list and get dispatch rules in parallel
427+ type listResult struct {
428+ resp * livekit.ListPhoneNumbersResponse
429+ err error
430+ }
431+ type dispatchRulesResult struct {
432+ rules map [string ][]string
433+ err error
434+ }
435+
436+ listChan := make (chan listResult , 1 )
437+ dispatchRulesChan := make (chan dispatchRulesResult , 1 )
438+
439+ // List phone numbers
440+ go func () {
441+ resp , err := client .ListPhoneNumbers (ctx , req )
442+ listChan <- listResult {resp : resp , err : err }
443+ }()
444+
445+ // Get dispatch rules mapping in parallel
446+ go func () {
447+ rules , err := getPhoneNumberToDispatchRulesMap (ctx , cmd )
448+ dispatchRulesChan <- dispatchRulesResult {rules : rules , err : err }
449+ }()
450+
451+ // Wait for list to complete
452+ listRes := <- listChan
453+ if listRes .err != nil {
454+ return listRes .err
455+ }
456+ resp := listRes .resp
457+
458+ // Wait for dispatch rules (ignore errors, we'll just not show them)
459+ dispatchRulesRes := <- dispatchRulesChan
460+ phoneNumberToRules := dispatchRulesRes .rules
461+ if dispatchRulesRes .err != nil {
462+ // Log but don't fail
463+ if cmd .Bool ("verbose" ) {
464+ fmt .Fprintf (cmd .ErrWriter , "Warning: failed to get dispatch rules: %v\n " , dispatchRulesRes .err )
465+ }
466+ phoneNumberToRules = make (map [string ][]string )
348467 }
349468
350469 if cmd .Bool ("json" ) {
@@ -356,8 +475,17 @@ func listPhoneNumbers(ctx context.Context, cmd *cli.Command) error {
356475 return listAndPrint (ctx , cmd , func (ctx context.Context , req * livekit.ListPhoneNumbersRequest ) (* livekit.ListPhoneNumbersResponse , error ) {
357476 return client .ListPhoneNumbers (ctx , req )
358477 }, req , []string {
359- "ID" , "E164" , "Country" , "Area Code" , "Type" , "Locality" , "Region" , "Capabilities" , "Status" , "SIP Dispatch Rule " ,
478+ "ID" , "E164" , "Country" , "Area Code" , "Type" , "Locality" , "Region" , "Capabilities" , "Status" , "SIP Dispatch Rules " ,
360479 }, func (item * livekit.PhoneNumber ) []string {
480+ rules := phoneNumberToRules [item .Id ]
481+ dispatchRulesStr := ""
482+ if len (rules ) > 0 {
483+ dispatchRulesStr = strings .Join (rules , ", " )
484+ } else if item .SipDispatchRuleId != "" {
485+ dispatchRulesStr = item .SipDispatchRuleId
486+ } else {
487+ dispatchRulesStr = "-"
488+ }
361489 return []string {
362490 item .Id ,
363491 item .E164Format ,
@@ -368,7 +496,7 @@ func listPhoneNumbers(ctx context.Context, cmd *cli.Command) error {
368496 item .Region ,
369497 strings .Join (item .Capabilities , "," ),
370498 strings .TrimPrefix (item .Status .String (), "PHONE_NUMBER_STATUS_" ),
371- item . SipDispatchRuleId ,
499+ dispatchRulesStr ,
372500 }
373501 })
374502}
@@ -396,9 +524,47 @@ func getPhoneNumber(ctx context.Context, cmd *cli.Command) error {
396524 req .PhoneNumber = & phoneNumber
397525 }
398526
399- resp , err := client .GetPhoneNumber (ctx , req )
400- if err != nil {
401- return err
527+ // Call get and get dispatch rules in parallel
528+ type getResult struct {
529+ resp * livekit.GetPhoneNumberResponse
530+ err error
531+ }
532+ type dispatchRulesResult struct {
533+ rules map [string ][]string
534+ err error
535+ }
536+
537+ getChan := make (chan getResult , 1 )
538+ dispatchRulesChan := make (chan dispatchRulesResult , 1 )
539+
540+ // Get phone number
541+ go func () {
542+ resp , err := client .GetPhoneNumber (ctx , req )
543+ getChan <- getResult {resp : resp , err : err }
544+ }()
545+
546+ // Get dispatch rules mapping in parallel
547+ go func () {
548+ rules , err := getPhoneNumberToDispatchRulesMap (ctx , cmd )
549+ dispatchRulesChan <- dispatchRulesResult {rules : rules , err : err }
550+ }()
551+
552+ // Wait for get to complete
553+ getRes := <- getChan
554+ if getRes .err != nil {
555+ return getRes .err
556+ }
557+ resp := getRes .resp
558+
559+ // Wait for dispatch rules (ignore errors, we'll just not show them)
560+ dispatchRulesRes := <- dispatchRulesChan
561+ phoneNumberToRules := dispatchRulesRes .rules
562+ if dispatchRulesRes .err != nil {
563+ // Log but don't fail
564+ if cmd .Bool ("verbose" ) {
565+ fmt .Fprintf (cmd .ErrWriter , "Warning: failed to get dispatch rules: %v\n " , dispatchRulesRes .err )
566+ }
567+ phoneNumberToRules = make (map [string ][]string )
402568 }
403569
404570 if cmd .Bool ("json" ) {
@@ -407,6 +573,16 @@ func getPhoneNumber(ctx context.Context, cmd *cli.Command) error {
407573 }
408574
409575 item := resp .PhoneNumber
576+ rules := phoneNumberToRules [item .Id ]
577+ dispatchRulesStr := ""
578+ if len (rules ) > 0 {
579+ dispatchRulesStr = strings .Join (rules , ", " )
580+ } else if item .SipDispatchRuleId != "" {
581+ dispatchRulesStr = item .SipDispatchRuleId
582+ } else {
583+ dispatchRulesStr = "-"
584+ }
585+
410586 fmt .Printf ("Phone Number Details:\n " )
411587 fmt .Printf (" ID: %s\n " , item .Id )
412588 fmt .Printf (" E164 Format: %s\n " , item .E164Format )
@@ -417,7 +593,7 @@ func getPhoneNumber(ctx context.Context, cmd *cli.Command) error {
417593 fmt .Printf (" Region: %s\n " , item .Region )
418594 fmt .Printf (" Capabilities: %s\n " , strings .Join (item .Capabilities , "," ))
419595 fmt .Printf (" Status: %s\n " , strings .TrimPrefix (item .Status .String (), "PHONE_NUMBER_STATUS_" ))
420- fmt .Printf (" SIP Dispatch Rule : %s\n " , item . SipDispatchRuleId )
596+ fmt .Printf (" SIP Dispatch Rules : %s\n " , dispatchRulesStr )
421597 if item .ReleasedAt != nil {
422598 fmt .Printf (" Released At: %s\n " , item .ReleasedAt .AsTime ().Format ("2006-01-02 15:04:05" ))
423599 }
@@ -450,10 +626,37 @@ func updatePhoneNumber(ctx context.Context, cmd *cli.Command) error {
450626 req .PhoneNumber = & phoneNumber
451627 }
452628
453- resp , err := client .UpdatePhoneNumber (ctx , req )
454- if err != nil {
455- return err
629+ // Call update and get dispatch rules in parallel
630+ type updateResult struct {
631+ resp * livekit.UpdatePhoneNumberResponse
632+ err error
633+ }
634+ type dispatchRulesResult struct {
635+ rules map [string ][]string
636+ err error
637+ }
638+
639+ updateChan := make (chan updateResult , 1 )
640+ dispatchRulesChan := make (chan dispatchRulesResult , 1 )
641+
642+ // Update phone number
643+ go func () {
644+ resp , err := client .UpdatePhoneNumber (ctx , req )
645+ updateChan <- updateResult {resp : resp , err : err }
646+ }()
647+
648+ // Get dispatch rules mapping in parallel
649+ go func () {
650+ rules , err := getPhoneNumberToDispatchRulesMap (ctx , cmd )
651+ dispatchRulesChan <- dispatchRulesResult {rules : rules , err : err }
652+ }()
653+
654+ // Wait for update to complete
655+ updateRes := <- updateChan
656+ if updateRes .err != nil {
657+ return updateRes .err
456658 }
659+ resp := updateRes .resp
457660
458661 // If dispatch rule ID was provided, append the phone number ID to the dispatch rule's trunk_ids
459662 dispatchRuleAdded := false
@@ -467,23 +670,58 @@ func updatePhoneNumber(ctx context.Context, cmd *cli.Command) error {
467670 }
468671 }
469672
673+ // Wait for dispatch rules (ignore errors, we'll just not show them)
674+ dispatchRulesRes := <- dispatchRulesChan
675+ phoneNumberToRules := dispatchRulesRes .rules
676+ if dispatchRulesRes .err != nil {
677+ // Log but don't fail
678+ if cmd .Bool ("verbose" ) {
679+ fmt .Fprintf (cmd .ErrWriter , "Warning: failed to get dispatch rules: %v\n " , dispatchRulesRes .err )
680+ }
681+ phoneNumberToRules = make (map [string ][]string )
682+ }
683+
684+ // Update the mapping with newly added dispatch rule if it was successfully added
685+ if dispatchRuleAdded && dispatchRuleID != "" {
686+ phoneNumberID := resp .PhoneNumber .Id
687+ // Check if it's already in the map (from the parallel fetch)
688+ if _ , exists := phoneNumberToRules [phoneNumberID ]; ! exists {
689+ phoneNumberToRules [phoneNumberID ] = []string {}
690+ }
691+ // Check if dispatchRuleID is already in the list
692+ found := false
693+ for _ , ruleID := range phoneNumberToRules [phoneNumberID ] {
694+ if ruleID == dispatchRuleID {
695+ found = true
696+ break
697+ }
698+ }
699+ if ! found {
700+ phoneNumberToRules [phoneNumberID ] = append (phoneNumberToRules [phoneNumberID ], dispatchRuleID )
701+ }
702+ }
703+
470704 if cmd .Bool ("json" ) {
471705 util .PrintJSON (resp )
472706 return nil
473707 }
474708
475709 item := resp .PhoneNumber
710+ rules := phoneNumberToRules [item .Id ]
711+ dispatchRulesStr := ""
712+ if len (rules ) > 0 {
713+ dispatchRulesStr = strings .Join (rules , ", " )
714+ } else if item .SipDispatchRuleId != "" {
715+ dispatchRulesStr = item .SipDispatchRuleId
716+ } else {
717+ dispatchRulesStr = "-"
718+ }
719+
476720 fmt .Printf ("Successfully updated phone number:\n " )
477721 fmt .Printf (" ID: %s\n " , item .Id )
478722 fmt .Printf (" E164 Format: %s\n " , item .E164Format )
479723 fmt .Printf (" Status: %s\n " , strings .TrimPrefix (item .Status .String (), "PHONE_NUMBER_STATUS_" ))
480-
481- // Show dispatch rule ID if it was provided and successfully added, or if it's in the response
482- displayRuleID := item .SipDispatchRuleId
483- if dispatchRuleAdded && dispatchRuleID != "" {
484- displayRuleID = dispatchRuleID
485- }
486- fmt .Printf (" SIP Dispatch Rule: %s\n " , displayRuleID )
724+ fmt .Printf (" SIP Dispatch Rules: %s\n " , dispatchRulesStr )
487725
488726 return nil
489727}
0 commit comments