Skip to content

Commit 1454db9

Browse files
Changing up how dispatch rules are assigned to numbers (#726)
* Changing up how dispatch rules are assigned to numbers * handling the case of multiple dispatch rules correctly * Fix static check issue * Moving the editing for trunk_ids to server
1 parent dd853b9 commit 1454db9

File tree

1 file changed

+274
-21
lines changed

1 file changed

+274
-21
lines changed

cmd/lk/phone_number.go

Lines changed: 274 additions & 21 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
func 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

Comments
 (0)