diff --git a/lib/NGCP/Panel/Controller/API/Calls.pm b/lib/NGCP/Panel/Controller/API/Calls.pm new file mode 100644 index 0000000000..3fb24dd025 --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/Calls.pm @@ -0,0 +1,173 @@ +package NGCP::Panel::Controller::API::Calls; +use Sipwise::Base; +use namespace::sweep; +use boolean qw(true); +use Data::HAL qw(); +use Data::HAL::Link qw(); +use HTTP::Headers qw(); +use HTTP::Status qw(:constants); +use MooseX::ClassAttribute qw(class_has); +use NGCP::Panel::Utils::DateTime; +use Path::Tiny qw(path); +use Safe::Isa qw($_isa); +BEGIN { extends 'Catalyst::Controller::ActionRole'; } +require Catalyst::ActionRole::ACL; +require Catalyst::ActionRole::CheckTrailingSlash; +require Catalyst::ActionRole::HTTPMethods; +require Catalyst::ActionRole::RequireSSL; + +class_has 'api_description' => ( + is => 'ro', + isa => 'Str', + default => + 'Defines calls placed or received by a customer.', +); + +class_has 'query_params' => ( + is => 'ro', + isa => 'ArrayRef', + default => sub {[ + { + param => 'customer_id', + description => 'Filter for calls of a specific customer', + query => { + first => sub { + my $q = shift; + { + -or => [ + { source_account_id => $q }, + { destination_account_id => $q }, + ], + }; + }, + second => sub {}, + }, + }, + { + param => 'subscriber_id', + description => 'Filter for calls of a specific subscriber', + query => { + first => sub { + my $q = shift; + return { + -or => [ + { 'source_subscriber.id' => $q }, + { 'destination_subscriber.id' => $q } + ], + }; + }, + second => sub { + return { + join => ['source_subscriber', 'destination_subscriber'], + }; + }, + }, + }, + ]}, +); + +with 'NGCP::Panel::Role::API::Calls'; + +class_has('resource_name', is => 'ro', default => 'calls'); +class_has('dispatch_path', is => 'ro', default => '/api/calls/'); +class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-calls'); + +__PACKAGE__->config( + action => { + map { $_ => { + ACLDetachTo => '/api/root/invalid_user', + AllowedRole => [qw/admin reseller/], + Args => 0, + Does => [qw(ACL CheckTrailingSlash 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) = @_; + my $page = $c->request->params->{page} // 1; + my $rows = $c->request->params->{rows} // 10; + { + my $items = $self->item_rs($c); + my $total_count = int($items->count); + $items = $items->search(undef, { + page => $page, + rows => $rows, + }); + my (@embedded, @links); + my $form = $self->get_form($c); + for my $item ($items->search({}, {order_by => {-asc => 'me.id'}})->all) { + push @embedded, $self->hal_from_item($c, $item, $form); + push @links, Data::HAL::Link->new( + relation => 'ngcp:'.$self->resource_name, + href => sprintf('/%s%d', $c->request->path, $item->id), + ); + } + push @links, + Data::HAL::Link->new( + relation => 'curies', + href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}', + name => 'ngcp', + 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)); + 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)); + } + if($page > 1) { + push @links, Data::HAL::Link->new(relation => 'prev', href => sprintf('/%s?page=%d&rows=%d', $c->request->path, $page - 1, $rows)); + } + + my $hal = Data::HAL->new( + embedded => [@embedded], + links => [@links], + ); + $hal->resource({ + total_count => $total_count, + }); + my $response = HTTP::Response->new(HTTP_OK, undef, + HTTP::Headers->new($hal->http_headers(skip_links => 1)), $hal->as_json); + $c->response->headers($response->headers); + $c->response->body($response->content); + return; + } + return; +} + +sub HEAD :Allow { + my ($self, $c) = @_; + $c->forward(qw(GET)); + $c->response->body(q()); + return; +} + +sub OPTIONS :Allow { + my ($self, $c) = @_; + my $allowed_methods = $self->allowed_methods; + $c->response->headers(HTTP::Headers->new( + Allow => $allowed_methods->join(', '), + Accept_Post => 'application/hal+json; profile=http://purl.org/sipwise/ngcp-api/#rel-'.$self->resource_name, + )); + $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/Controller/API/CallsItem.pm b/lib/NGCP/Panel/Controller/API/CallsItem.pm new file mode 100644 index 0000000000..a8b0793c43 --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/CallsItem.pm @@ -0,0 +1,91 @@ +package NGCP::Panel::Controller::API::CallsItem; +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::Calls'; + +class_has('resource_name', is => 'ro', default => 'calls'); +class_has('dispatch_path', is => 'ro', default => '/api/calls/'); +class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-calls'); + +__PACKAGE__->config( + action => { + map { $_ => { + ACLDetachTo => '/api/root/invalid_user', + AllowedRole => [qw/admin reseller/], + 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) = @_; + { + last unless $self->valid_id($c, $id); + my $item = $self->item_by_id($c, $id); + last unless $self->resource_exists($c, call => $item); + + my $hal = $self->hal_from_item($c, $item); + + 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; + $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/Controller/API/Root.pm b/lib/NGCP/Panel/Controller/API/Root.pm index 0484920ed3..8fe0feb670 100644 --- a/lib/NGCP/Panel/Controller/API/Root.pm +++ b/lib/NGCP/Panel/Controller/API/Root.pm @@ -67,7 +67,7 @@ sub GET : Allow { $query_params = $full_mod->query_params; } my $actions = [ keys %{ $full_mod->config->{action} } ]; - my $item_actions = [ keys %{ $full_item_mod->config->{action} } ]; + my $item_actions = $full_item_mod->can('config') ? [ keys %{ $full_item_mod->config->{action} } ] : []; my $form = $full_mod->get_form($c); diff --git a/lib/NGCP/Panel/Form/Call/Admin.pm b/lib/NGCP/Panel/Form/Call/Admin.pm new file mode 100644 index 0000000000..717c941518 --- /dev/null +++ b/lib/NGCP/Panel/Form/Call/Admin.pm @@ -0,0 +1,149 @@ +package NGCP::Panel::Form::Call::Admin; + +use HTML::FormHandler::Moose; +extends 'NGCP::Panel::Form::Call::Reseller'; +use Moose::Util::TypeConstraints; + +has_field 'source_carrier_cost' => ( + type => 'Float', + element_attr => { + rel => ['tooltip'], + title => ['The cost for the operator towards the peering carrier.'] + }, +); + +has_field 'source_carrier_free_time' => ( + type => 'PosInteger', + element_attr => { + rel => ['tooltip'], + title => ['The number of free seconds of the carrier contract for this call.'] + }, +); + +has_field 'source_carrier_billing_fee_id' => ( + type => 'PosInteger', + element_attr => { + rel => ['tooltip'], + title => ['The billing fee id used to calculate the source carrier cost.'] + }, +); + +has_field 'source_reseller_billing_fee_id' => ( + type => 'PosInteger', + element_attr => { + rel => ['tooltip'], + title => ['The billing fee id used to calculate the source reseller cost.'] + }, +); + +has_field 'source_carrier_billing_zone_id' => ( + type => 'PosInteger', + element_attr => { + rel => ['tooltip'], + title => ['The source billing zone id (from billing.billing_zones_history) attached to the carrier billing cost.'] + }, +); + +has_field 'source_reseller_billing_zone_id' => ( + type => 'PosInteger', + element_attr => { + rel => ['tooltip'], + title => ['The source billing zone id (from billing.billing_zones_history) attached to the reseller billing cost.'] + }, +); + +has_field 'destination_carrier_cost' => ( + type => 'Float', + element_attr => { + rel => ['tooltip'], + title => ['The cost for the carrier towards the operator (e.g. for 800-numbers).'] + }, +); + +has_field 'destination_carrier_free_time' => ( + type => 'PosInteger', + element_attr => { + rel => ['tooltip'], + title => ['The number of free seconds of the carrier contract for this call.'] + }, +); + +has_field 'destination_carrier_billing_fee_id' => ( + type => 'PosInteger', + element_attr => { + rel => ['tooltip'], + title => ['The billing fee id used to calculate the destination carrier cost.'] + }, +); + +has_field 'destination_reseller_billing_fee_id' => ( + type => 'PosInteger', + element_attr => { + rel => ['tooltip'], + title => ['The billing fee id used to calculate the destination reseller cost.'] + }, +); + +has_field 'destination_carrier_billing_zone_id' => ( + type => 'PosInteger', + element_attr => { + rel => ['tooltip'], + title => ['The destination billing zone id (from billing.billing_zones_history) attached to the carrier billing cost.'] + }, +); + +has_field 'destination_reseller_billing_zone_id' => ( + type => 'PosInteger', + element_attr => { + rel => ['tooltip'], + title => ['The destination billing zone id (from billing.billing_zones_history) attached to the reseller billing cost.'] + }, +); + +has_field 'rated_at' => ( + type => 'Text', + element_attr => { + rel => ['tooltip'], + title => ['The timestamp when the rating occured.'] + }, + required => 0, +); + +has_field 'rating_status' => ( + type => 'Select', + options => [ + { label => 'unrated', 'value' => 'unrated' }, + { label => 'ok', 'value' => 'ok' }, + { label => 'failed', 'value' => 'failed' }, + ], + element_attr => { + rel => ['tooltip'], + title => ['The status of the rating process.'] + }, +); + +has_field 'exported_at' => ( + type => 'Text', + element_attr => { + rel => ['tooltip'], + title => ['The timestamp when the exporting occured.'] + }, + required => 0, +); + +has_field 'export_status' => ( + type => 'Select', + options => [ + { label => 'unexported', 'value' => 'unexported' }, + { label => 'ok', 'value' => 'ok' }, + { label => 'failed', 'value' => 'failed' }, + ], + element_attr => { + rel => ['tooltip'], + title => ['The status of the exporting process.'] + }, +); + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/Call/Reseller.pm b/lib/NGCP/Panel/Form/Call/Reseller.pm new file mode 100644 index 0000000000..bad8aba479 --- /dev/null +++ b/lib/NGCP/Panel/Form/Call/Reseller.pm @@ -0,0 +1,406 @@ +package NGCP::Panel::Form::Call::Reseller; + +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler'; +use Moose::Util::TypeConstraints; + +use HTML::FormHandler::Widget::Block::Bootstrap; + +has '+widget_wrapper' => ( default => 'Bootstrap' ); +has_field 'submitid' => ( type => 'Hidden' ); + +has_field 'source_user_id' => ( + type => 'Text', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['UUID of calling subscriber, or 0 if from external.'] + }, +); + +has_field 'source_provider_id' => ( + type => 'Text', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['Reseller contract id of calling subscriber, or contract id of peer if from external.'] + }, +); + +has_field 'source_external_subscriber_id' => ( + type => 'Text', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['External ID of calling subscriber, if local.'] + }, +); + +has_field 'source_external_contract_id' => ( + type => 'Text', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['External ID of the calling subscriber\'s customer, if local.'] + }, +); + +has_field 'source_customer_id' => ( + type => 'Text', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['Customer id of calling subscriber, if local.'] + }, +); + +has_field 'source_user' => ( + type => 'Text', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['Username of calling party.'] + }, +); + +has_field 'source_domain' => ( + type => 'Text', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['Domain of calling party.'] + }, +); + +has_field 'source_cli' => ( + type => 'Text', + label => 'Source CLI', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['Normalized CLI (usually E164) of calling party.'] + }, +); + +has_field 'source_clir' => ( + type => 'Boolean', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['Whether calling party number was suppressed (CLIR).'] + }, +); + +has_field 'source_ip' => ( + type => 'Text', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['IP address of calling party.'] + }, +); + +has_field 'destination_user_id' => ( + type => 'Text', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['UUID of called subscriber, or 0 if to external.'] + }, +); + +has_field 'destination_provider_id' => ( + type => 'Text', + label => 'Reseller id of called subscriber, or contract id of peer if to external', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['Reseller contract id of called subscriber, or contract id of peer if to external.'] + }, +); + +has_field 'destination_external_subscriber_id' => ( + type => 'Text', + label => 'external_id of called subscriber', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['External id of called subscriber, if local.'] + }, +); + +has_field 'destination_external_contract_id' => ( + type => 'Text', + label => 'external_id of called subscriber', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['External id of called subscriber\'s customer, if local.'] + }, +); + +has_field 'destination_customer_id' => ( + type => 'Text', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['Customer id of called subscriber, if local.'] + }, +); + +has_field 'destination_user' => ( + type => 'Text', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['Username or number of called party.'] + }, +); + +has_field 'destination_domain' => ( + type => 'Text', + label => 'Destination Domain', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['Domain of called party.'] + }, +); + +has_field 'destination_user_dialed' => ( + type => 'Text', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['Destination username or number as received by the system from calling party before any internal rewriting.'] + }, +); + +has_field 'destination_user_in' => ( + type => 'Text', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['Destination username or number as received by the system from calling party after internal rewriting.'] + }, +); + +has_field 'destination_domain_in' => ( + type => 'Text', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['Destination domain as received by the system from calling party after internal rewriting.'] + }, +); + +has_field 'peer_auth_user' => ( + type => 'Text', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The authentication username used for outbound authentication.'] + }, +); + +has_field 'peer_auth_realm' => ( + type => 'Text', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The authentication realm (domain) used for outbound authentication.'] + }, +); + +has_field 'call_type' => ( + type => 'Select', + required => 1, + options => [ + { label => 'call', value => 'call' }, + { label => 'cfu', value => 'cfu' }, + { label => 'cfb', value => 'cfb' }, + { label => 'cft', value => 'cft' }, + { label => 'cfna', value => 'cfna' }, + ], + element_attr => { + rel => ['tooltip'], + title => ['The type of call, one of call, cfu, cfb, cft, cfna.'] + }, +); + +has_field 'call_status' => ( + type => 'Select', + required => 1, + options => [ + { label => 'ok', value => 'ok' }, + { label => 'busy', value => 'busy' }, + { label => 'noanswer', value => 'noanswer' }, + { label => 'cancel', value => 'cancel' }, + { label => 'offline', value => 'offline' }, + { label => 'timeout', value => 'timeout' }, + { label => 'other', value => 'other' }, + ], + element_attr => { + rel => ['tooltip'], + title => ['The status of the call, one of ok, busy, noanswer, cancel, offline, timeout, other.'] + }, +); + +has_field 'call_code' => ( + type => 'Text', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The final SIP response code of the call.'] + }, +); + +has_field 'init_time' => ( + type => 'Text', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The timestamp of the call initiation.'] + }, +); + +has_field 'start_time' => ( + type => 'Text', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The timestamp of the call connection.'] + }, +); + +has_field 'duration' => ( + type => 'PosInteger', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The duration of the call.'] + }, +); + +has_field 'call_id' => ( + type => 'Text', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The value of the SIP Call-ID header for this call.'] + }, +); + +has_field 'source_reseller_cost' => ( + type => 'Float', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The cost for the reseller of the calling party towards the system operator.'] + }, +); + +has_field 'source_customer_cost' => ( + type => 'Float', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The cost for the calling party customer towards the reseller.'] + }, +); + +has_field 'source_reseller_free_time' => ( + type => 'PosInteger', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The number of free seconds of the reseller used for this call.'] + }, +); + +has_field 'source_customer_free_time' => ( + type => 'PosInteger', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The number of free seconds of the customer used for this call.'] + }, +); + +has_field 'source_customer_billing_fee_id' => ( + type => 'PosInteger', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The billing fee id used to calculate the source customer cost.'] + }, +); + +has_field 'source_customer_billing_zone_id' => ( + type => 'PosInteger', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The source billing zone id (from billing.billing_zones_history) attached to the customer billing cost.'] + }, +); + +has_field 'destination_reseller_cost' => ( + type => 'Float', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The cost for the reseller of the called party towards the system operator.'] + }, +); + +has_field 'destination_customer_cost' => ( + type => 'Float', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The cost for the called party customer towards the reseller.'] + }, +); + +has_field 'destination_reseller_free_time' => ( + type => 'PosInteger', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The number of free seconds of the reseller used for this call.'] + }, +); + +has_field 'destination_customer_free_time' => ( + type => 'PosInteger', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The number of free seconds of the customer used for this call.'] + }, +); + +has_field 'destination_customer_billing_fee_id' => ( + type => 'PosInteger', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The billing fee id used to calculate the destination customer cost.'] + }, +); + +has_field 'destination_customer_billing_zone_id' => ( + type => 'PosInteger', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The destination billing zone id (from billing.billing_zones_history) attached to the customer billing cost.'] + }, +); + + + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Role/API.pm b/lib/NGCP/Panel/Role/API.pm index 4c78ef3381..0779d4892b 100644 --- a/lib/NGCP/Panel/Role/API.pm +++ b/lib/NGCP/Panel/Role/API.pm @@ -76,12 +76,15 @@ sub validate_form { my $resource = $params{resource}; my $form = $params{form}; my $run = $params{run} // 1; + my $exceptions = $params{exceptions} // []; + push @{ $exceptions }, "external_id"; my @normalized = (); # move {xxx_id} into {xxx}{id} for FormHandler foreach my $key(keys %{ $resource } ) { - if($key =~ /^(.+)_id$/ && $key ne "external_id") { + my $skip_normalize = grep {/^$key$/} @{ $exceptions }; + if($key =~ /^(.+)_id$/ && !$skip_normalize && !exists $resource->{$1}) { push @normalized, $1; $resource->{$1}{id} = delete $resource->{$key}; } diff --git a/lib/NGCP/Panel/Role/API/Calls.pm b/lib/NGCP/Panel/Role/API/Calls.pm new file mode 100644 index 0000000000..7ab6843a80 --- /dev/null +++ b/lib/NGCP/Panel/Role/API/Calls.pm @@ -0,0 +1,133 @@ +package NGCP::Panel::Role::API::Calls; +use Moose::Role; +use Sipwise::Base; +with 'NGCP::Panel::Role::API' => { + -alias =>{ item_rs => '_item_rs', }, + -excludes => [ 'item_rs' ], +}; + +use boolean qw(true); +use TryCatch; +use Data::HAL qw(); +use Data::HAL::Link qw(); +use HTTP::Status qw(:constants); +use NGCP::Panel::Form::Call::Admin; +use NGCP::Panel::Form::Call::Reseller; + +sub item_rs { + my ($self, $c) = @_; + + my $item_rs = $c->model('DB')->resultset('cdr'); + if($c->user->roles eq "admin") { + } elsif($c->user->roles eq "reseller") { + print ">>>>>>>>>>>>>> filtering s/d_provider_id " . $c->user->reseller->contract_id . "\n"; + $item_rs = $item_rs->search({ + -or => [ + { source_provider_id => $c->user->reseller->contract_id }, + { destination_provider_id => $c->user->reseller->contract_id }, + ], + }); + } + return $item_rs; +} + +sub get_form { + my ($self, $c) = @_; + if($c->user->roles eq "admin") { + return NGCP::Panel::Form::Call::Admin->new; + } elsif($c->user->roles eq "reseller") { + return NGCP::Panel::Form::Call::Reseller->new; + } +} + +sub hal_from_item { + my ($self, $c, $item, $form) = @_; + my $resource = $self->resource_from_item($c, $item, $form); + + my $hal = Data::HAL->new( + links => [ + Data::HAL::Link->new( + relation => 'curies', + href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}', + name => 'ngcp', + templated => true, + ), + 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)), + # 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)), + ], + relation => 'ngcp:'.$self->resource_name, + ); + + $form //= $self->get_form($c); + + $self->validate_form( + c => $c, + resource => $resource, + form => $form, + run => 0, + exceptions => [ + "source_provider_id", "destination_provider_id", + "source_external_subscriber_id", "destination_external_subscriber_id", + "source_external_contract_id", "destination_external_contract_id", + "source_carrier_billing_fee_id", "destination_carrier_billing_fee_id", + "source_reseller_billing_fee_id", "destination_reseller_billing_fee_id", + "source_customer_billing_fee_id", "destination_customer_billing_fee_id", + "source_carrier_billing_zone_id", "destination_carrier_billing_zone_id", + "source_reseller_billing_zone_id", "destination_reseller_billing_zone_id", + "source_customer_billing_zone_id", "destination_customer_billing_zone_id", + ], + ); + + $resource->{id} = int($item->id); + $hal->resource($resource); + return $hal; +} + +sub resource_from_item { + my ($self, $c, $item, $form) = @_; + my $resource = { $item->get_inflated_columns }; + + $resource->{source_customer_id} = delete $resource->{source_account_id}; + $resource->{destination_customer_id} = delete $resource->{destination_account_id}; + + if($c->user->roles eq "reseller") { + my @filter = (); + if($item->source_provider_id ne "".$c->user->reseller->contract_id) { + push @filter, (qw/ + source_user_id source_provider_id + source_external_subscriber_id source_external_contract_id + source_customer_id source_ip + source_reseller_cost source_customer_cost + source_reseller_free_time source_customer_free_time + source_customer_billing_fee_id source_customer_billing_zone_id + /); + } + if($item->destination_provider_id ne "".$c->user->reseller->contract_id) { + push @filter, (qw/ + destination_user_id destination_provider_id + destination_external_subscriber_id destination_external_contract_id + destination_customer_id + destination_reseller_cost destination_customer_cost + destination_reseller_free_time destination_customer_free_time + destination_customer_billing_fee_id destination_customer_billing_zone_id + /); + } + for my $f(@filter) { + $resource->{$f} = undef if exists($resource->{$f}); + } + } + + return $resource; +} + +sub item_by_id { + my ($self, $c, $id) = @_; + my $item_rs = $self->item_rs($c); + return $item_rs->find($id); +} + +1; +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Role/API/Customers.pm b/lib/NGCP/Panel/Role/API/Customers.pm index be39901709..42e94acfc9 100644 --- a/lib/NGCP/Panel/Role/API/Customers.pm +++ b/lib/NGCP/Panel/Role/API/Customers.pm @@ -100,6 +100,7 @@ sub hal_from_customer { Data::HAL::Link->new(relation => 'ngcp:contractbalances', href => sprintf("/api/contractbalances/%d", $contract_balance->id)), $customer->subscriber_email_template_id ? (Data::HAL::Link->new(relation => 'ngcp:subscriberemailtemplates', href => sprintf("/api/emailtemplates/%d", $customer->subscriber_email_template_id))) : (), $customer->passreset_email_template_id ? (Data::HAL::Link->new(relation => 'ngcp:passresetemailtemplates', href => sprintf("/api/emailtemplates/%d", $customer->passreset_email_template_id))) : (), + Data::HAL::Link->new(relation => 'ngcp:calls', href => sprintf("/api/calls/?customer_id=%d", $customer->id)), ], relation => 'ngcp:'.$self->resource_name, ); diff --git a/lib/NGCP/Panel/Role/API/Subscribers.pm b/lib/NGCP/Panel/Role/API/Subscribers.pm index 5b31e1ea85..ca27d30a98 100644 --- a/lib/NGCP/Panel/Role/API/Subscribers.pm +++ b/lib/NGCP/Panel/Role/API/Subscribers.pm @@ -104,6 +104,7 @@ sub hal_from_item { Data::HAL::Link->new(relation => 'ngcp:customers', href => sprintf("/api/customers/%d", $item->contract_id)), ($item->provisioning_voip_subscriber && $item->provisioning_voip_subscriber->profile_set_id) ? (Data::HAL::Link->new(relation => 'ngcp:subscriberprofilesets', href => sprintf("/api/subscriberprofilesets/%d", $item->provisioning_voip_subscriber->profile_set_id))) : (), ($item->provisioning_voip_subscriber && $item->provisioning_voip_subscriber->profile_id) ? (Data::HAL::Link->new(relation => 'ngcp:subscriberprofiles', href => sprintf("/api/subscriberprofiles/%d", $item->provisioning_voip_subscriber->profile_id))) : (), + Data::HAL::Link->new(relation => 'ngcp:calls', href => sprintf("/api/calls/?subscriber_id=%d", $item->id)), #Data::HAL::Link->new(relation => 'ngcp:registrations', href => sprintf("/api/registrations/%d", $item->contract->id)), #Data::HAL::Link->new(relation => 'ngcp:trustedsources', href => sprintf("/api/trustedsources/%d", $item->contract->id)), ], diff --git a/share/templates/api/root/collection.tt b/share/templates/api/root/collection.tt index 2441eaf723..32ef0a2c06 100644 --- a/share/templates/api/root/collection.tt +++ b/share/templates/api/root/collection.tt @@ -13,9 +13,9 @@ [% col.description %] Collection Actions -Allowed methods for the collection as in METHOD [% uri %] +Allowed methods for the collection as in METHOD [% uri %]: [% UNLESS col.actions.size -%] -None. +
None. [% ELSE -%]