diff --git a/lib/NGCP/Panel/Controller/API/CallLists.pm b/lib/NGCP/Panel/Controller/API/CallLists.pm index 74560a7812..0ae4dd4952 100644 --- a/lib/NGCP/Panel/Controller/API/CallLists.pm +++ b/lib/NGCP/Panel/Controller/API/CallLists.pm @@ -47,6 +47,15 @@ class_has 'query_params' => ( }, }, }, + { + param => 'alias_field', + description => 'Set this parameter for example to "gpp0" if you store alias numbers in the gpp0 preference and want to have that value shown as other CLI for calls from or to such a local subscriber.', + query => { + # handled directly in role + first => sub {}, + second => sub {}, + }, + }, { param => 'status', description => 'Filter for calls with a specific status. One of "ok", "busy", "noanswer", "cancel", "offline", "timeout", "other".', @@ -153,26 +162,8 @@ sub GET :Allow { my $rows = $c->request->params->{rows} // 10; my $schema = $c->model('DB'); { - my $sub; - if($c->user->roles ne "subscriber") { - unless($c->req->param('subscriber_id')) { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Mandatory parameter 'subscriber_id' missing in request"); - last; - } - $sub = $schema->resultset('voip_subscribers')->find($c->req->param('subscriber_id')); - unless($sub) { - $self->error($c, HTTP_NOT_FOUND, "Invalid 'subscriber_id'."); - last; - } - if(($c->user->roles eq "subscriberadmin" && $sub->contract_id != $c->user->account_id) || - ($c->user->roles eq "reseller" && $sub->contract->contact->reseller_id != $c->user->reseller_id)) { - $self->error($c, HTTP_NOT_FOUND, "Invalid 'subscriber_id'."); - last; - } - } else { - $sub = $c->user->voip_subscriber; - } - + my $sub = $self->get_subscriber($c, $schema); + last unless $sub; my $items = $self->item_rs($c); (my $total_count, $items) = $self->paginate_order_collection($c, $items); my (@embedded, @links); @@ -181,7 +172,7 @@ sub GET :Allow { push @embedded, $self->hal_from_item($c, $item, $sub, $form); push @links, Data::HAL::Link->new( relation => 'ngcp:'.$self->resource_name, - href => sprintf('/%s%d', $c->request->path, $item->id), + href => sprintf('/%s%d?subscriber_id=%d', $c->request->path, $item->id, $sub->id), ); } push @links, @@ -192,7 +183,7 @@ sub GET :Allow { templated => true, ), Data::HAL::Link->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'), - Data::HAL::Link->new(relation => 'self', href => sprintf('/%s?page=%s&rows=%s', $c->request->path, $page, $rows)); + Data::HAL::Link->new(relation => 'self', href => sprintf('/%s?page=%s&rows=%s&subscriber_id=%d', $c->request->path, $page, $rows, $sub->id)); if(($total_count / $rows) > $page ) { push @links, Data::HAL::Link->new(relation => 'next', href => sprintf('/%s?page=%d&rows=%d', $c->request->path, $page + 1, $rows)); } diff --git a/lib/NGCP/Panel/Controller/API/CallListsItem.pm b/lib/NGCP/Panel/Controller/API/CallListsItem.pm new file mode 100644 index 0000000000..88b2e2a2d0 --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/CallListsItem.pm @@ -0,0 +1,94 @@ +package NGCP::Panel::Controller::API::CallListsItem; +use Sipwise::Base; +use namespace::sweep; +use HTTP::Headers qw(); +use HTTP::Status qw(:constants); +use MooseX::ClassAttribute qw(class_has); +use NGCP::Panel::Utils::DateTime; +use NGCP::Panel::Utils::ValidateJSON qw(); +use Path::Tiny qw(path); +use Safe::Isa qw($_isa); +BEGIN { extends 'Catalyst::Controller::ActionRole'; } +require Catalyst::ActionRole::ACL; +require Catalyst::ActionRole::HTTPMethods; +require Catalyst::ActionRole::RequireSSL; + +with 'NGCP::Panel::Role::API::CallLists'; + +class_has('resource_name', is => 'ro', default => 'calllists'); +class_has('dispatch_path', is => 'ro', default => '/api/calllists/'); +class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-calllists'); + +__PACKAGE__->config( + action => { + map { $_ => { + ACLDetachTo => '/api/root/invalid_user', + AllowedRole => [qw/admin reseller subscriberadmin subscriber/], + Args => 1, + Does => [qw(ACL RequireSSL)], + Method => $_, + Path => __PACKAGE__->dispatch_path, + } } @{ __PACKAGE__->allowed_methods } + }, + action_roles => [qw(HTTPMethods)], +); + +sub auto :Private { + my ($self, $c) = @_; + + $self->set_body($c); + $self->log_request($c); +} + +sub GET :Allow { + my ($self, $c, $id) = @_; + { + my $schema = $c->model('DB'); + last unless $self->valid_id($c, $id); + my $sub = $self->get_subscriber($c, $schema); + last unless $sub; + my $item = $self->item_by_id($c, $id); + last unless $self->resource_exists($c, calllist => $item); + + my $hal = $self->hal_from_item($c, $item, $sub); + + my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new( + (map { # XXX Data::HAL must be able to generate links with multiple relations + s|rel="(http://purl.org/sipwise/ngcp-api/#rel-resellers)"|rel="item $1"|; + s/rel=self/rel="item self"/; + $_ + } $hal->http_headers), + ), $hal->as_json); + $c->response->headers($response->headers); + $c->response->body($response->content); + return; + } + return; +} + +sub HEAD :Allow { + my ($self, $c, $id) = @_; + $c->forward(qw(GET)); + $c->response->body(q()); + return; +} + +sub OPTIONS :Allow { + my ($self, $c, $id) = @_; + my $allowed_methods = $self->allowed_methods_filtered($c); + $c->response->headers(HTTP::Headers->new( + Allow => $allowed_methods->join(', '), + Accept_Patch => 'application/json-patch+json', + )); + $c->response->content_type('application/json'); + $c->response->body(JSON::to_json({ methods => $allowed_methods })."\n"); + return; +} + +sub end : Private { + my ($self, $c) = @_; + + $self->log_response($c); +} + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/CallList/Subscriber.pm b/lib/NGCP/Panel/Form/CallList/Subscriber.pm index 8886061ee3..2b8a541103 100644 --- a/lib/NGCP/Panel/Form/CallList/Subscriber.pm +++ b/lib/NGCP/Panel/Form/CallList/Subscriber.pm @@ -22,6 +22,15 @@ has_field 'direction' => ( }, ); +has_field 'own_cli' => ( + type => 'Text', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The CLI of the own party.'] + }, +); + has_field 'other_cli' => ( type => 'Text', required => 1, @@ -101,6 +110,15 @@ has_field 'customer_free_time' => ( }, ); +has_field 'intra_customer' => ( + type => 'Boolean', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['Whether it is a call between subscribers of one single customer.'] + }, +); + 1; # vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Role/API/CallLists.pm b/lib/NGCP/Panel/Role/API/CallLists.pm index 1f34399d9d..f30ba2f8ff 100644 --- a/lib/NGCP/Panel/Role/API/CallLists.pm +++ b/lib/NGCP/Panel/Role/API/CallLists.pm @@ -74,7 +74,7 @@ sub hal_from_item { ), Data::HAL::Link->new(relation => 'collection', href => sprintf("/api/%s/", $self->resource_name)), Data::HAL::Link->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'), - Data::HAL::Link->new(relation => 'self', href => sprintf("%s%d", $self->dispatch_path, $item->id)), + Data::HAL::Link->new(relation => 'self', href => sprintf("%s%d?subscriber_id=%d", $self->dispatch_path, $item->id, $sub->id)), # todo: customer can be in source_account_id or destination_account_id # Data::HAL::Link->new(relation => 'ngcp:customers', href => sprintf("/api/customers/%d", $item->source_customer_id)), ], @@ -103,15 +103,86 @@ sub resource_from_item { pattern => '%F %T', ); + my $intra = 0; + if($item->source_user_id && $item->source_account_id == $item->destination_account_id) { + $resource->{intra_customer} = JSON::true; + $intra = 1; + } else { + $resource->{intra_customer} = JSON::false; + $intra = 0; + } $resource->{direction} = $sub->uuid eq $item->source_user_id ? "out" : "in"; - $resource->{other_cli} = $resource->{direction} eq "out" ? - $item->destination_user_in : $item->source_cli; + + my ($src_sub, $dst_sub); + if($item->source_subscriber && $item->source_subscriber->provisioning_voip_subscriber) { + $src_sub = $item->source_subscriber->provisioning_voip_subscriber; + } + if($item->destination_subscriber && $item->destination_subscriber->provisioning_voip_subscriber) { + $dst_sub = $item->destination_subscriber->provisioning_voip_subscriber; + } + my ($own_normalize, $other_normalize); + + if($resource->{direction} eq "out") { + # for pbx out calls, use extension as own cli + if($src_sub && $src_sub->pbx_extension) { + $resource->{own_cli} = $src_sub->pbx_extension; + } else { + $resource->{own_cli} = $item->source_cli; + $own_normalize = 1; + } + + # for intra pbx out calls, use extension as other cli + if($intra && $dst_sub && $dst_sub->pbx_extension) { + $resource->{other_cli} = $dst_sub->pbx_extension; + # if there is an alias field (e.g. gpp0), use this + } elsif($item->destination_account_id && $c->req->param('alias_field')) { + my $alias = $item->get_column('destination_'.$c->req->param('alias_field')); + $c->log->error("+++++++++++++++++ alias_field=".$c->req->param('alias_field')); + $c->log->error("+++++++++++++++++ destination_alias_field=".($alias // '')); + $resource->{other_cli} = $alias // $item->destination_user_in; + $other_normalize = 1; + } else { + $resource->{other_cli} = $item->destination_user_in; + $other_normalize = 1; + } + } else { + # for pbx in calls, use extension as own cli + if($dst_sub && $dst_sub->pbx_extension) { + $resource->{own_cli} = $dst_sub->pbx_extension; + } else { + $resource->{own_cli} = $item->destination_user_in; + $own_normalize = 1; + } + + # for intra pbx in calls, use extension as other cli + if($intra && $src_sub && $src_sub->pbx_extension) { + $resource->{other_cli} = $src_sub->pbx_extension; + # if there is an alias field (e.g. gpp0), use this + } elsif($item->source_account_id && $c->req->param('alias_field')) { + my $alias = $item->get_column('source_'.$c->req->param('alias_field')); + $resource->{other_cli} = $alias // $item->source_cli; + $other_normalize = 1; + } else { + $resource->{other_cli} = $item->source_cli; + $other_normalize = 1; + } + } + + if($resource->{own_cli} !~ /^\d+$/) { + $resource->{own_cli} .= '@'.$sub->domain->domain; + } elsif($own_normalize) { + $resource->{own_cli} = NGCP::Panel::Utils::Subscriber::apply_rewrite( + c => $c, subscriber => $sub, + number => $resource->{own_cli}, direction => "caller_out" + ); + } + if($resource->{direction} eq "in" && $item->source_clir) { $resource->{other_cli} = undef; } elsif($resource->{other_cli} !~ /^\d+$/) { - $resource->{other_cli} .= '@'.$item->destination_domain_in; - } else { + $resource->{other_cli} .= '@'. ($resource->{direction} eq "in" ? $item->destination_domain_in : $sub->domain->domain); + } elsif($other_normalize) { $resource->{other_cli} = NGCP::Panel::Utils::Subscriber::apply_rewrite( c => $c, subscriber => $sub, number => $resource->{other_cli}, direction => "caller_out" @@ -136,5 +207,30 @@ sub item_by_id { return $item_rs->find($id); } +sub get_subscriber { + my ($self, $c, $schema) = @_; + + my $sub; + if($c->user->roles ne "subscriber") { + unless($c->req->param('subscriber_id')) { + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Mandatory parameter 'subscriber_id' missing in request"); + return; + } + $sub = $schema->resultset('voip_subscribers')->find($c->req->param('subscriber_id')); + unless($sub) { + $self->error($c, HTTP_NOT_FOUND, "Invalid 'subscriber_id'."); + return; + } + if(($c->user->roles eq "subscriberadmin" && $sub->contract_id != $c->user->account_id) || + ($c->user->roles eq "reseller" && $sub->contract->contact->reseller_id != $c->user->reseller_id)) { + $self->error($c, HTTP_NOT_FOUND, "Invalid 'subscriber_id'."); + return; + } + } else { + $sub = $c->user->voip_subscriber; + } + return $sub; +} + 1; # vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Utils/DateTime.pm b/lib/NGCP/Panel/Utils/DateTime.pm index b8dd90badd..f6399d8d3f 100644 --- a/lib/NGCP/Panel/Utils/DateTime.pm +++ b/lib/NGCP/Panel/Utils/DateTime.pm @@ -44,6 +44,19 @@ sub new_local { ); } +# convert seconds to 'HH:MM:SS' format +sub sec_to_hms +{ + use integer; + local $_ = shift; + my ($h, $m, $s); + $s = sprintf("%02d", $_ % 60); $_ /= 60; + $m = sprintf("%02d", $_ % 60); $_ /= 60; + $h = $_; + return "$h:$m:$s"; +} + + 1; # vim: set tabstop=4 expandtab: