@@ -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+
164194func searchPhoneNumbers (ctx context.Context , cmd * cli.Command ) error {
165195 client , err := createPhoneNumberClient (ctx , cmd )
166196 if err != nil {
@@ -226,16 +256,74 @@ func purchasePhoneNumbers(ctx context.Context, cmd *cli.Command) error {
226256 return fmt .Errorf ("at least one phone number must be provided" )
227257 }
228258
259+ dispatchRuleID := cmd .String ("sip-dispatch-rule-id" )
260+
229261 req := & livekit.PurchasePhoneNumberRequest {
230262 PhoneNumbers : phoneNumbers ,
231263 }
232- if val := cmd . String ( "sip-dispatch-rule-id" ); val != "" {
233- req .SipDispatchRuleId = & val
264+ if dispatchRuleID != "" {
265+ req .SipDispatchRuleId = & dispatchRuleID
234266 }
235267
236- resp , err := client .PurchasePhoneNumber (ctx , req )
237- if err != nil {
238- return err
268+ // Call purchase and get dispatch rules in parallel
269+ type purchaseResult struct {
270+ resp * livekit.PurchasePhoneNumberResponse
271+ err error
272+ }
273+ type dispatchRulesResult struct {
274+ rules map [string ][]string
275+ err error
276+ }
277+
278+ purchaseChan := make (chan purchaseResult , 1 )
279+ dispatchRulesChan := make (chan dispatchRulesResult , 1 )
280+
281+ // Purchase phone numbers
282+ go func () {
283+ resp , err := client .PurchasePhoneNumber (ctx , req )
284+ purchaseChan <- purchaseResult {resp : resp , err : err }
285+ }()
286+
287+ // Get dispatch rules mapping in parallel
288+ go func () {
289+ rules , err := getPhoneNumberToDispatchRulesMap (ctx , cmd )
290+ dispatchRulesChan <- dispatchRulesResult {rules : rules , err : err }
291+ }()
292+
293+ // Wait for purchase to complete
294+ purchaseRes := <- purchaseChan
295+ if purchaseRes .err != nil {
296+ return purchaseRes .err
297+ }
298+ resp := purchaseRes .resp
299+
300+ // Wait for dispatch rules (ignore errors, we'll just not show them)
301+ dispatchRulesRes := <- dispatchRulesChan
302+ phoneNumberToRules := dispatchRulesRes .rules
303+ if dispatchRulesRes .err != nil {
304+ // Log but don't fail
305+ if cmd .Bool ("verbose" ) {
306+ fmt .Fprintf (cmd .ErrWriter , "Warning: failed to get dispatch rules: %v\n " , dispatchRulesRes .err )
307+ }
308+ phoneNumberToRules = make (map [string ][]string )
309+ }
310+
311+ // If dispatch rule ID was provided, add it to the mapping for display
312+ // (The actual update is now handled by cloud-io)
313+ if dispatchRuleID != "" {
314+ for _ , phoneNumber := range resp .PhoneNumbers {
315+ // Check if dispatchRuleID is already in the list
316+ found := false
317+ for _ , ruleID := range phoneNumberToRules [phoneNumber .Id ] {
318+ if ruleID == dispatchRuleID {
319+ found = true
320+ break
321+ }
322+ }
323+ if ! found {
324+ phoneNumberToRules [phoneNumber .Id ] = append (phoneNumberToRules [phoneNumber .Id ], dispatchRuleID )
325+ }
326+ }
239327 }
240328
241329 if cmd .Bool ("json" ) {
@@ -245,7 +333,12 @@ func purchasePhoneNumbers(ctx context.Context, cmd *cli.Command) error {
245333
246334 fmt .Printf ("Successfully purchased %d phone numbers:\n " , len (resp .PhoneNumbers ))
247335 for _ , phoneNumber := range resp .PhoneNumbers {
248- fmt .Printf (" %s (%s) - %s\n " , phoneNumber .E164Format , phoneNumber .Id , strings .TrimPrefix (phoneNumber .Status .String (), "PHONE_NUMBER_STATUS_" ))
336+ ruleInfo := ""
337+ rules := phoneNumberToRules [phoneNumber .Id ]
338+ if len (rules ) > 0 {
339+ ruleInfo = fmt .Sprintf (" (SIP Dispatch Rules: %s)" , strings .Join (rules , ", " ))
340+ }
341+ fmt .Printf (" %s (%s) - %s%s\n " , phoneNumber .E164Format , phoneNumber .Id , strings .TrimPrefix (phoneNumber .Status .String (), "PHONE_NUMBER_STATUS_" ), ruleInfo )
249342 }
250343
251344 return nil
@@ -277,9 +370,47 @@ func listPhoneNumbers(ctx context.Context, cmd *cli.Command) error {
277370 req .SipDispatchRuleId = & val
278371 }
279372
280- resp , err := client .ListPhoneNumbers (ctx , req )
281- if err != nil {
282- return err
373+ // Call list and get dispatch rules in parallel
374+ type listResult struct {
375+ resp * livekit.ListPhoneNumbersResponse
376+ err error
377+ }
378+ type dispatchRulesResult struct {
379+ rules map [string ][]string
380+ err error
381+ }
382+
383+ listChan := make (chan listResult , 1 )
384+ dispatchRulesChan := make (chan dispatchRulesResult , 1 )
385+
386+ // List phone numbers
387+ go func () {
388+ resp , err := client .ListPhoneNumbers (ctx , req )
389+ listChan <- listResult {resp : resp , err : err }
390+ }()
391+
392+ // Get dispatch rules mapping in parallel
393+ go func () {
394+ rules , err := getPhoneNumberToDispatchRulesMap (ctx , cmd )
395+ dispatchRulesChan <- dispatchRulesResult {rules : rules , err : err }
396+ }()
397+
398+ // Wait for list to complete
399+ listRes := <- listChan
400+ if listRes .err != nil {
401+ return listRes .err
402+ }
403+ resp := listRes .resp
404+
405+ // Wait for dispatch rules (ignore errors, we'll just not show them)
406+ dispatchRulesRes := <- dispatchRulesChan
407+ phoneNumberToRules := dispatchRulesRes .rules
408+ if dispatchRulesRes .err != nil {
409+ // Log but don't fail
410+ if cmd .Bool ("verbose" ) {
411+ fmt .Fprintf (cmd .ErrWriter , "Warning: failed to get dispatch rules: %v\n " , dispatchRulesRes .err )
412+ }
413+ phoneNumberToRules = make (map [string ][]string )
283414 }
284415
285416 if cmd .Bool ("json" ) {
@@ -291,8 +422,17 @@ func listPhoneNumbers(ctx context.Context, cmd *cli.Command) error {
291422 return listAndPrint (ctx , cmd , func (ctx context.Context , req * livekit.ListPhoneNumbersRequest ) (* livekit.ListPhoneNumbersResponse , error ) {
292423 return client .ListPhoneNumbers (ctx , req )
293424 }, req , []string {
294- "ID" , "E164" , "Country" , "Area Code" , "Type" , "Locality" , "Region" , "Capabilities" , "Status" , "SIP Dispatch Rule " ,
425+ "ID" , "E164" , "Country" , "Area Code" , "Type" , "Locality" , "Region" , "Capabilities" , "Status" , "SIP Dispatch Rules " ,
295426 }, func (item * livekit.PhoneNumber ) []string {
427+ rules := phoneNumberToRules [item .Id ]
428+ dispatchRulesStr := ""
429+ if len (rules ) > 0 {
430+ dispatchRulesStr = strings .Join (rules , ", " )
431+ } else if item .SipDispatchRuleId != "" {
432+ dispatchRulesStr = item .SipDispatchRuleId
433+ } else {
434+ dispatchRulesStr = "-"
435+ }
296436 return []string {
297437 item .Id ,
298438 item .E164Format ,
@@ -303,7 +443,7 @@ func listPhoneNumbers(ctx context.Context, cmd *cli.Command) error {
303443 item .Region ,
304444 strings .Join (item .Capabilities , "," ),
305445 strings .TrimPrefix (item .Status .String (), "PHONE_NUMBER_STATUS_" ),
306- item . SipDispatchRuleId ,
446+ dispatchRulesStr ,
307447 }
308448 })
309449}
@@ -331,9 +471,47 @@ func getPhoneNumber(ctx context.Context, cmd *cli.Command) error {
331471 req .PhoneNumber = & phoneNumber
332472 }
333473
334- resp , err := client .GetPhoneNumber (ctx , req )
335- if err != nil {
336- return err
474+ // Call get and get dispatch rules in parallel
475+ type getResult struct {
476+ resp * livekit.GetPhoneNumberResponse
477+ err error
478+ }
479+ type dispatchRulesResult struct {
480+ rules map [string ][]string
481+ err error
482+ }
483+
484+ getChan := make (chan getResult , 1 )
485+ dispatchRulesChan := make (chan dispatchRulesResult , 1 )
486+
487+ // Get phone number
488+ go func () {
489+ resp , err := client .GetPhoneNumber (ctx , req )
490+ getChan <- getResult {resp : resp , err : err }
491+ }()
492+
493+ // Get dispatch rules mapping in parallel
494+ go func () {
495+ rules , err := getPhoneNumberToDispatchRulesMap (ctx , cmd )
496+ dispatchRulesChan <- dispatchRulesResult {rules : rules , err : err }
497+ }()
498+
499+ // Wait for get to complete
500+ getRes := <- getChan
501+ if getRes .err != nil {
502+ return getRes .err
503+ }
504+ resp := getRes .resp
505+
506+ // Wait for dispatch rules (ignore errors, we'll just not show them)
507+ dispatchRulesRes := <- dispatchRulesChan
508+ phoneNumberToRules := dispatchRulesRes .rules
509+ if dispatchRulesRes .err != nil {
510+ // Log but don't fail
511+ if cmd .Bool ("verbose" ) {
512+ fmt .Fprintf (cmd .ErrWriter , "Warning: failed to get dispatch rules: %v\n " , dispatchRulesRes .err )
513+ }
514+ phoneNumberToRules = make (map [string ][]string )
337515 }
338516
339517 if cmd .Bool ("json" ) {
@@ -342,6 +520,16 @@ func getPhoneNumber(ctx context.Context, cmd *cli.Command) error {
342520 }
343521
344522 item := resp .PhoneNumber
523+ rules := phoneNumberToRules [item .Id ]
524+ dispatchRulesStr := ""
525+ if len (rules ) > 0 {
526+ dispatchRulesStr = strings .Join (rules , ", " )
527+ } else if item .SipDispatchRuleId != "" {
528+ dispatchRulesStr = item .SipDispatchRuleId
529+ } else {
530+ dispatchRulesStr = "-"
531+ }
532+
345533 fmt .Printf ("Phone Number Details:\n " )
346534 fmt .Printf (" ID: %s\n " , item .Id )
347535 fmt .Printf (" E164 Format: %s\n " , item .E164Format )
@@ -352,7 +540,7 @@ func getPhoneNumber(ctx context.Context, cmd *cli.Command) error {
352540 fmt .Printf (" Region: %s\n " , item .Region )
353541 fmt .Printf (" Capabilities: %s\n " , strings .Join (item .Capabilities , "," ))
354542 fmt .Printf (" Status: %s\n " , strings .TrimPrefix (item .Status .String (), "PHONE_NUMBER_STATUS_" ))
355- fmt .Printf (" SIP Dispatch Rule : %s\n " , item . SipDispatchRuleId )
543+ fmt .Printf (" SIP Dispatch Rules : %s\n " , dispatchRulesStr )
356544 if item .ReleasedAt != nil {
357545 fmt .Printf (" Released At: %s\n " , item .ReleasedAt .AsTime ().Format ("2006-01-02 15:04:05" ))
358546 }
@@ -376,19 +564,76 @@ func updatePhoneNumber(ctx context.Context, cmd *cli.Command) error {
376564 return fmt .Errorf ("only one of --id or --number can be provided" )
377565 }
378566
567+ dispatchRuleID := cmd .String ("sip-dispatch-rule-id" )
568+
379569 req := & livekit.UpdatePhoneNumberRequest {}
380570 if id != "" {
381571 req .Id = & id
382572 } else {
383573 req .PhoneNumber = & phoneNumber
384574 }
385- if val := cmd . String ( "sip-dispatch-rule-id" ); val != "" {
386- req .SipDispatchRuleId = & val
575+ if dispatchRuleID != "" {
576+ req .SipDispatchRuleId = & dispatchRuleID
387577 }
388578
389- resp , err := client .UpdatePhoneNumber (ctx , req )
390- if err != nil {
391- return err
579+ // Call update and get dispatch rules in parallel
580+ type updateResult struct {
581+ resp * livekit.UpdatePhoneNumberResponse
582+ err error
583+ }
584+ type dispatchRulesResult struct {
585+ rules map [string ][]string
586+ err error
587+ }
588+
589+ updateChan := make (chan updateResult , 1 )
590+ dispatchRulesChan := make (chan dispatchRulesResult , 1 )
591+
592+ // Update phone number
593+ go func () {
594+ resp , err := client .UpdatePhoneNumber (ctx , req )
595+ updateChan <- updateResult {resp : resp , err : err }
596+ }()
597+
598+ // Get dispatch rules mapping in parallel
599+ go func () {
600+ rules , err := getPhoneNumberToDispatchRulesMap (ctx , cmd )
601+ dispatchRulesChan <- dispatchRulesResult {rules : rules , err : err }
602+ }()
603+
604+ // Wait for update to complete
605+ updateRes := <- updateChan
606+ if updateRes .err != nil {
607+ return updateRes .err
608+ }
609+ resp := updateRes .resp
610+
611+ // Wait for dispatch rules (ignore errors, we'll just not show them)
612+ dispatchRulesRes := <- dispatchRulesChan
613+ phoneNumberToRules := dispatchRulesRes .rules
614+ if dispatchRulesRes .err != nil {
615+ // Log but don't fail
616+ if cmd .Bool ("verbose" ) {
617+ fmt .Fprintf (cmd .ErrWriter , "Warning: failed to get dispatch rules: %v\n " , dispatchRulesRes .err )
618+ }
619+ phoneNumberToRules = make (map [string ][]string )
620+ }
621+
622+ // If dispatch rule ID was provided, add it to the mapping for display
623+ // (The actual update is now handled by cloud-io)
624+ if dispatchRuleID != "" {
625+ phoneNumberID := resp .PhoneNumber .Id
626+ // Check if dispatchRuleID is already in the list
627+ found := false
628+ for _ , ruleID := range phoneNumberToRules [phoneNumberID ] {
629+ if ruleID == dispatchRuleID {
630+ found = true
631+ break
632+ }
633+ }
634+ if ! found {
635+ phoneNumberToRules [phoneNumberID ] = append (phoneNumberToRules [phoneNumberID ], dispatchRuleID )
636+ }
392637 }
393638
394639 if cmd .Bool ("json" ) {
@@ -397,11 +642,19 @@ func updatePhoneNumber(ctx context.Context, cmd *cli.Command) error {
397642 }
398643
399644 item := resp .PhoneNumber
645+ rules := phoneNumberToRules [item .Id ]
646+ dispatchRulesStr := ""
647+ if len (rules ) > 0 {
648+ dispatchRulesStr = strings .Join (rules , ", " )
649+ } else {
650+ dispatchRulesStr = "-"
651+ }
652+
400653 fmt .Printf ("Successfully updated phone number:\n " )
401654 fmt .Printf (" ID: %s\n " , item .Id )
402655 fmt .Printf (" E164 Format: %s\n " , item .E164Format )
403656 fmt .Printf (" Status: %s\n " , strings .TrimPrefix (item .Status .String (), "PHONE_NUMBER_STATUS_" ))
404- fmt .Printf (" SIP Dispatch Rule : %s\n " , item . SipDispatchRuleId )
657+ fmt .Printf (" SIP Dispatch Rules : %s\n " , dispatchRulesStr )
405658
406659 return nil
407660}
0 commit comments