diff --git a/lib/NGCP/Panel/Controller/API/SubscriberRegistrations.pm b/lib/NGCP/Panel/Controller/API/SubscriberRegistrations.pm new file mode 100644 index 0000000000..26c418fe0f --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/SubscriberRegistrations.pm @@ -0,0 +1,160 @@ +package NGCP::Panel::Controller::API::SubscriberRegistrations; +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 registered contacts of subscribers.', +); + +class_has 'query_params' => ( + is => 'ro', + isa => 'ArrayRef', + default => sub {[ + ]}, +); + +with 'NGCP::Panel::Role::API::SubscriberRegistrations'; + +class_has('resource_name', is => 'ro', default => 'subscriberregistrations'); +class_has('dispatch_path', is => 'ro', default => '/api/subscriberregistrations/'); +class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-subscriberregistrations'); + +__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 POST :Allow { + my ($self, $c) = @_; + + { + my $resource = $self->get_valid_post_data( + c => $c, + media_type => 'application/json', + ); + last unless $resource; + + my $form = $self->get_form($c); + my $create = 1; + $self->update_item($c, undef, undef, $resource, $form, $create); + + $c->response->status(HTTP_CREATED); + # TODO: can we somehow get our item back from an xmlrpc call? probably not as + # it is async + #$c->response->header(Location => sprintf('/%s%d', $c->request->path, $item->id)); + $c->response->body(q()); + } + return; +} + +sub end : Private { + my ($self, $c) = @_; + + $self->log_response($c); +} + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Controller/API/SubscriberRegistrationsItem.pm b/lib/NGCP/Panel/Controller/API/SubscriberRegistrationsItem.pm new file mode 100644 index 0000000000..96894fda25 --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/SubscriberRegistrationsItem.pm @@ -0,0 +1,188 @@ +package NGCP::Panel::Controller::API::SubscriberRegistrationsItem; +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::SubscriberRegistrations'; + +class_has('resource_name', is => 'ro', default => 'subscriberregistrations'); +class_has('dispatch_path', is => 'ro', default => '/api/subscriberregistrations/'); +class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-subscriberregistrations'); + +__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, subscriberregistration => $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 PATCH :Allow { + my ($self, $c, $id) = @_; + my $guard = $c->model('DB')->txn_scope_guard; + { + my $preference = $self->require_preference($c); + last unless $preference; + + my $json = $self->get_valid_patch_data( + c => $c, + id => $id, + media_type => 'application/json-patch+json', + ); + last unless $json; + + my $item = $self->item_by_id($c, $id); + last unless $self->resource_exists($c, subscriberregistration => $item); + my $form = $self->get_form($c); + my $old_resource = $self->resource_from_item($c, $item, $form); + my $resource = $self->apply_patch($c, $old_resource, $json); + last unless $resource; + + $item = $self->update_item($c, $item, $old_resource, $resource, $form); + last unless $item; + + $guard->commit; + + if ('minimal' eq $preference) { + $c->response->status(HTTP_NO_CONTENT); + $c->response->header(Preference_Applied => 'return=minimal'); + $c->response->body(q()); + } else { + my $hal = $self->hal_from_item($c, $item, $form); + my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new( + $hal->http_headers, + ), $hal->as_json); + $c->response->headers($response->headers); + $c->response->header(Preference_Applied => 'return=representation'); + $c->response->body($response->content); + } + } + return; +} + +sub PUT :Allow { + my ($self, $c, $id) = @_; + my $guard = $c->model('DB')->txn_scope_guard; + { + my $preference = $self->require_preference($c); + last unless $preference; + + my $item = $self->item_by_id($c, $id); + last unless $self->resource_exists($c, subscriberregistration => $item); + my $resource = $self->get_valid_put_data( + c => $c, + id => $id, + media_type => 'application/json', + ); + last unless $resource; + my $form = $self->get_form($c); + my $old_resource = $self->resource_from_item($c, $item, $form); + + $item = $self->update_item($c, $item, $old_resource, $resource, $form); + last unless $item; + + $guard->commit; + + if ('minimal' eq $preference) { + $c->response->status(HTTP_NO_CONTENT); + $c->response->header(Preference_Applied => 'return=minimal'); + $c->response->body(q()); + } else { + my $hal = $self->hal_from_item($c, $item, $form); + my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new( + $hal->http_headers, + ), $hal->as_json); + $c->response->headers($response->headers); + $c->response->header(Preference_Applied => 'return=representation'); + $c->response->body($response->content); + } + } + return; +} + +sub DELETE :Allow { + my ($self, $c, $id) = @_; + + { + my $item = $self->item_by_id($c, $id); + last unless $self->resource_exists($c, subscriberregistration => $item); + $self->delete_item($c, $item); + + $c->response->status(HTTP_NO_CONTENT); + $c->response->body(q()); + } + return; +} + +sub end : Private { + my ($self, $c) = @_; + + $self->log_response($c); +} + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Controller/Subscriber.pm b/lib/NGCP/Panel/Controller/Subscriber.pm index 4475aa212a..2d62947a33 100644 --- a/lib/NGCP/Panel/Controller/Subscriber.pm +++ b/lib/NGCP/Panel/Controller/Subscriber.pm @@ -15,6 +15,7 @@ use NGCP::Panel::Utils::Message; use NGCP::Panel::Utils::DateTime; use NGCP::Panel::Utils::Sems; use NGCP::Panel::Utils::Hylafax; +use NGCP::Panel::Utils::Kamailio; use NGCP::Panel::Form::Subscriber; use NGCP::Panel::Form::SubscriberEdit; use NGCP::Panel::Form::Customer::PbxSubscriberEdit; @@ -2871,21 +2872,9 @@ sub delete_registered :Chained('registered') :PathPart('delete') :Args(0) { my $ret; try { - my $s = $c->stash->{subscriber}->provisioning_voip_subscriber; - my $aor = $s->username . '@' . $s->domain->domain; - my $contact = $c->stash->{registered}->contact; - my $dispatcher = NGCP::Panel::Utils::XMLDispatcher->new; - $ret = $dispatcher->dispatch("proxy-ng", 1, 1, < - -ul.rm_contact - -location -$aor -$contact - - -EOF + NGCP::Panel::Utils::Kamailio::delete_location_contact($c, + $c->stash->{subscriber}->provisioning_voip_subscriber, + $c->stash->{registered}->contact); } catch($e) { NGCP::Panel::Utils::Message->error( c => $c, @@ -2894,12 +2883,6 @@ EOF ); } -# TODO: how to determine if $ret was ok? -# unless($ret) { -# $c->log->error("failed to delete registered device: $e"); -# $c->flash(messages => [{type => 'error', text => 'Failed to delete registered device'}]); -# } - $c->flash(messages => [{type => 'success', text => $c->loc('Successfully deleted registered device')}]); NGCP::Panel::Utils::Navigation::back_or($c, $c->uri_for_action('/subscriber/details', [$c->req->captures->[0]])); @@ -2925,30 +2908,11 @@ sub create_registered :Chained('master') :PathPart('registered/create') :Args(0) ); if($posted && $form->validated) { try { - my $s = $c->stash->{subscriber}->provisioning_voip_subscriber; - my $aor = $s->username . '@' . $s->domain->domain; - my $contact = $form->field('contact')->value; - my $q = $form->field('q')->value; - my $path = $c->config->{sip}->{path} || ''; - my $dispatcher = NGCP::Panel::Utils::XMLDispatcher->new; - $ret = $dispatcher->dispatch("proxy-ng", 1, 1, < - -ul.add - -location -$aor -$contact -0 -$q - -0 -0 -4294967295 - - -EOF - # TODO: error check + NGCP::Panel::Utils::Kamailio::create_location($c, + $c->stash->{subscriber}->provisioning_voip_subscriber, + $form->field('contact')->value, + $form->field('q')->value + ); $c->flash(messages => [{type => 'success', text => $c->loc('Successfully added registered device')}]); } catch($e) { NGCP::Panel::Utils::Message->error( diff --git a/lib/NGCP/Panel/Form/Subscriber/RegisteredAPI.pm b/lib/NGCP/Panel/Form/Subscriber/RegisteredAPI.pm new file mode 100644 index 0000000000..4fc79c1927 --- /dev/null +++ b/lib/NGCP/Panel/Form/Subscriber/RegisteredAPI.pm @@ -0,0 +1,79 @@ +package NGCP::Panel::Form::Subscriber::RegisteredAPI; + +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 'subscriber_id' => ( + type => 'PosInteger', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The subscriber the contact belongs to.'] + }, +); + +has_field 'contact' => ( + type => 'Text', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The SIP URI pointing to the current contact of the subscriber.'] + }, +); + +has_field 'expires' => ( + type => 'Text', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The expire timestamp of the registered contact.'] + }, +); + +has_field 'user_agent' => ( + type => 'Text', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The user agent registered at this contact.'] + }, +); + +has_field 'q' => ( + type => 'Float', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The priority (q-value) of the registration.'] + }, +); + +has_field 'nat' => ( + type => 'Boolean', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The registered contact is detected as behind NAT.'] + }, +); + +=pod +sub validate { + my $self = shift; + my $attach = $self->field('attach')->value; + my $delete = $self->field('delete')->value; + if($delete && !$attach) { + $self->field('attach')->add_error('Must be set if delete is set'); + } +} +=cut + + +1; +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Role/API/Calls.pm b/lib/NGCP/Panel/Role/API/Calls.pm index 7ab6843a80..9994638f2a 100644 --- a/lib/NGCP/Panel/Role/API/Calls.pm +++ b/lib/NGCP/Panel/Role/API/Calls.pm @@ -20,7 +20,6 @@ sub item_rs { 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 }, diff --git a/lib/NGCP/Panel/Role/API/SubscriberRegistrations.pm b/lib/NGCP/Panel/Role/API/SubscriberRegistrations.pm new file mode 100644 index 0000000000..d3ee74939a --- /dev/null +++ b/lib/NGCP/Panel/Role/API/SubscriberRegistrations.pm @@ -0,0 +1,184 @@ +package NGCP::Panel::Role::API::SubscriberRegistrations; +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::Subscriber::RegisteredAPI; +use NGCP::Panel::Utils::Kamailio; + +sub item_rs { + my ($self, $c) = @_; + + my $item_rs = $c->model('DB')->resultset('location'); + if($c->user->roles eq "admin") { + } elsif($c->user->roles eq "reseller") { + $item_rs = $item_rs->search({ + 'contact.reseller_id' => $c->user->reseller_id + },{ + join => { } + }); + } + return $item_rs; +} + +sub get_form { + my ($self, $c) = @_; + return NGCP::Panel::Form::Subscriber::RegisteredAPI->new; +} + +sub hal_from_item { + my ($self, $c, $item, $form) = @_; + $form //= $self->get_form($c); + 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)), + Data::HAL::Link->new(relation => 'ngcp:subscribers', href => sprintf("/api/subscribers/%d", $resource->{subscriber_id})), + ], + relation => 'ngcp:'.$self->resource_name, + ); + + $self->validate_form( + c => $c, + resource => $resource, + form => $form, + run => 0, + exceptions => [ "subscriber_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 }; + + my $sub = $self->subscriber_from_item($c, $item); + return unless($sub); + $resource->{subscriber_id} = int($sub->id); + $resource->{nat} = $resource->{cflags} & 64; + + return $resource; +} + +sub item_by_id { + my ($self, $c, $id) = @_; + my $item_rs = $self->item_rs($c); + return $item_rs->find($id); +} + +sub subscriber_from_item { + my ($self, $c, $item) = @_; + + my $sub_rs = $c->model('DB')->resultset('voip_subscribers')->search({ + username => $item->username, + status => { '!=' => 'terminated' }, + }); + if($c->config->{features}->{multidomain}) { + $sub_rs = $sub_rs->search({ + 'domain.domain' => $item->domain, + }, { + join => 'domain', + }); + } + my $sub = $sub_rs->first; + unless($sub && $sub->provisioning_voip_subscriber) { + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "No subscriber for location entry found"); + return; + } + return $sub; +} + +sub subscriber_from_id { + my ($self, $c, $id) = @_; + + my $sub_rs = $c->model('DB')->resultset('voip_subscribers')->search({ + id => $id, + status => { '!=' => 'terminated' }, + }); + if($c->user->roles eq "admin") { + } elsif($c->user->roles eq "reseller") { + $sub_rs = $sub_rs->search({ + 'contact.reseller_id' => $c->user->reseller_id, + },{ + join => { contract => 'contact' }, + }); + } + my $sub = $sub_rs->first; + unless($sub && $sub->provisioning_voip_subscriber) { + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "No subscriber for subscriber_id found"); + return; + } + return $sub; +} + +sub update_item { + my ($self, $c, $item, $old_resource, $resource, $form, $create) = @_; + + $form //= $self->get_form($c); + return unless $self->validate_form( + c => $c, + form => $form, + resource => $resource, + exceptions => [ "subscriber_id" ], + ); + + my $sub = $self->subscriber_from_id($c, $resource->{subscriber_id}); + return unless($sub); + + unless($create) { + $self->delete_item($c, $item); + } + my $cflags = 0; + $cflags |= 64 if($resource->{nat}); + NGCP::Panel::Utils::Kamailio::create_location($c, + $sub->provisioning_voip_subscriber, + $resource->{contact}, + $resource->{q}, + $resource->{expires}, + 0, # flags + $cflags + ); + + unless($create) { + # we need to reload it since we changed the content via an external + # xmlrpc call + $item->discard_changes; + + return $item; + } +} + +sub delete_item { + my ($self, $c, $item) = @_; + + my $sub = $self->subscriber_from_item($c, $item); + return unless($sub); + NGCP::Panel::Utils::Kamailio::delete_location_contact($c, + $sub, $item->contact); + return 1; +} + +1; +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Utils/DateTime.pm b/lib/NGCP/Panel/Utils/DateTime.pm index dd640e33b5..0cd7b60639 100644 --- a/lib/NGCP/Panel/Utils/DateTime.pm +++ b/lib/NGCP/Panel/Utils/DateTime.pm @@ -1,6 +1,8 @@ package NGCP::Panel::Utils::DateTime; use Sipwise::Base; +use DateTime; +use DateTime::Format::ISO8601; sub current_local { return DateTime->now( @@ -16,6 +18,15 @@ sub epoch_local { ); } +sub from_string { + my $s = shift; + # just for convenience, if date is passed like xxxx-xx-xx xx:xx:xx, + # convert it to xxxx-xx-xxTxx:xx:xx + $s =~ s/^(\d{4}\-\d{2}\-\d{2})\s+(\d.+)$/$1T$2/; + my $ts = DateTime::Format::ISO8601->parse_datetime($s); + return $ts; +} + 1; # vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Utils/Kamailio.pm b/lib/NGCP/Panel/Utils/Kamailio.pm new file mode 100644 index 0000000000..d1a9afc1e3 --- /dev/null +++ b/lib/NGCP/Panel/Utils/Kamailio.pm @@ -0,0 +1,79 @@ +package NGCP::Panel::Utils::Kamailio; + +use Sipwise::Base; +use NGCP::Panel::Utils::XMLDispatcher; +use NGCP::Panel::Utils::DateTime; +use Data::Dumper; + +sub delete_location_contact { + my ($c, $prov_subscriber, $contact) = @_; + + my $aor = $prov_subscriber->username . '@' . $prov_subscriber->domain->domain; + my $dispatcher = NGCP::Panel::Utils::XMLDispatcher->new; + my $ret = $dispatcher->dispatch("proxy-ng", 1, 1, < + +ul.rm_contact + +location +$aor +$contact + + +EOF + +} + +sub delete_location { + my ($c, $prov_subscriber) = @_; + + my $aor = $prov_subscriber->username . '@' . $prov_subscriber->domain->domain; + my $dispatcher = NGCP::Panel::Utils::XMLDispatcher->new; + my $ret = $dispatcher->dispatch("proxy-ng", 1, 1, < + +ul.rm + +location +$aor + + +EOF + +} + +sub create_location { + my ($c, $prov_subscriber, $contact, $q, $expires, $flags, $cflags) = @_; + + my $aor = $prov_subscriber->username . '@' . $prov_subscriber->domain->domain; + my $path = $c->config->{sip}->{path} || ''; + if($expires) { + $expires = NGCP::Panel::Utils::DateTime::from_string($expires)->epoch; + } else { + $expires = 4294967295; + } + $flags //= 0; + $cflags //= 0; + my $dispatcher = NGCP::Panel::Utils::XMLDispatcher->new; + my $ret = $dispatcher->dispatch("proxy-ng", 1, 1, < + +ul.add + +location +$aor +$contact +0 +$q + +$flags +$cflags +$expires + + +EOF +} + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Utils/Subscriber.pm b/lib/NGCP/Panel/Utils/Subscriber.pm index 5528a65545..6c4d4ea10b 100644 --- a/lib/NGCP/Panel/Utils/Subscriber.pm +++ b/lib/NGCP/Panel/Utils/Subscriber.pm @@ -616,9 +616,6 @@ sub update_subadmin_sub_aliases { my $contract_id = $params{contract_id}; my $alias_selected = $params{alias_selected}; - print ">>>>>>>>>> update sub aliases\n"; - use Data::Printer; p %params; - my $num_rs = $schema->resultset('voip_numbers')->search_rs({ 'subscriber.contract_id' => $contract_id, },{ diff --git a/share/templates/api/root/intro.tt b/share/templates/api/root/intro.tt index 8a7b4ebb15..61e77eab8f 100644 --- a/share/templates/api/root/intro.tt +++ b/share/templates/api/root/intro.tt @@ -69,4 +69,22 @@ Due to the JSON-HAL structure, all related resources are hyperlinked, which impl As a consequence, this means that a client implemented against the API should not hardcode URIs, rather than using the hyperlinks provided in the resources. +Query Parameters + + +Some collections define query paramters to filter the output. Typical use cases are limiting collections to a specific reseller or a specific customer. + +Query parameters are appended to the URL like in the following example: + + +curl -X GET 'https://example.org:1443/api/somecollection/?param_one=1¶m2=something + + +Some query parameters allow wildcard/pattern matching, which is expressed by a '*' like this: + + +curl -X GET 'https://example.org:1443/api/somecollection/?param=*something* + + + [% # vim: set tabstop=4 syntax=html expandtab: -%]