Skip to content

Commit b047b95

Browse files
handling the case of multiple dispatch rules correctly
1 parent 8f4ce65 commit b047b95

File tree

1 file changed

+266
-28
lines changed

1 file changed

+266
-28
lines changed

cmd/lk/phone_number.go

Lines changed: 266 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -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
165195
func 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

Comments
 (0)