diff --git a/lib/NGCP/Panel/Controller/API/PeeringGroups.pm b/lib/NGCP/Panel/Controller/API/PeeringGroups.pm
new file mode 100644
index 0000000000..a7f0ddf26d
--- /dev/null
+++ b/lib/NGCP/Panel/Controller/API/PeeringGroups.pm
@@ -0,0 +1,209 @@
+package NGCP::Panel::Controller::API::PeeringGroups;
+use NGCP::Panel::Utils::Generic qw(:all);
+use Sipwise::Base;
+use Moose;
+#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 NGCP::Panel::Utils::Peering;
+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 peering groups.',
+);
+
+class_has 'query_params' => (
+ is => 'ro',
+ isa => 'ArrayRef',
+ default => sub {[
+ {
+ param => 'name',
+ description => 'Filter for peering group name',
+ query => {
+ first => sub {
+ my $q = shift;
+ { name => { like => $q } };
+ },
+ second => sub {},
+ },
+ },
+ {
+ param => 'description',
+ description => 'Filter for peering group description',
+ query => {
+ first => sub {
+ my $q = shift;
+ { description => { like => $q } };
+ },
+ second => sub {},
+ },
+ },
+ ]},
+);
+
+with 'NGCP::Panel::Role::API::PeeringGroups';
+
+class_has('resource_name', is => 'ro', default => 'peeringgroups');
+class_has('dispatch_path', is => 'ro', default => '/api/peeringgroups/');
+class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-peeringgroups');
+
+__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, $items) = $self->paginate_order_collection($c, $items);
+ my (@embedded, @links);
+ my $form = $self->get_form($c);
+ for my $item ($items->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_filtered($c);
+ $c->response->headers(HTTP::Headers->new(
+ Allow => join(', ', @{ $allowed_methods }),
+ 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 $guard = $c->model('DB')->txn_scope_guard;
+ {
+ my $resource = $self->get_valid_post_data(
+ c => $c,
+ media_type => 'application/json',
+ );
+ last unless $resource;
+
+ my $form = $self->get_form($c);
+
+ last unless $self->validate_form(
+ c => $c,
+ resource => $resource,
+ form => $form,
+ );
+ $resource = $form->custom_get_values;
+ last unless $resource;
+ my $item;
+ my $dup_item = $c->model('DB')->resultset('voip_peer_groups')->find({
+ name => $resource->{name},
+ });
+ if($dup_item) {
+ $c->log->error("peering group with name '$$resource{name}' already exists"); # TODO: user, message, trace, ...
+ $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Peering group with this name already exists");
+ last;
+ }
+
+ try {
+ $item = $c->model('DB')->resultset('voip_peer_groups')->create($resource);
+ NGCP::Panel::Utils::Peering::_sip_lcr_reload(c => $c);
+ } catch($e) {
+ $c->log->error("failed to create peering group: $e"); # TODO: user, message, trace, ...
+ $self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create peering group.");
+ last;
+ }
+
+ $guard->commit;
+
+ $c->response->status(HTTP_CREATED);
+ $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);
+}
+
+no Moose;
+1;
+
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Controller/API/PeeringGroupsItem.pm b/lib/NGCP/Panel/Controller/API/PeeringGroupsItem.pm
new file mode 100644
index 0000000000..8343bc591a
--- /dev/null
+++ b/lib/NGCP/Panel/Controller/API/PeeringGroupsItem.pm
@@ -0,0 +1,201 @@
+package NGCP::Panel::Controller::API::PeeringGroupsItem;
+use NGCP::Panel::Utils::Generic qw(:all);
+use Sipwise::Base;
+use Moose;
+#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::PeeringGroups';
+
+class_has('resource_name', is => 'ro', default => 'peeringgroups');
+class_has('dispatch_path', is => 'ro', default => '/api/peeringgroups/');
+class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-peeringgroups');
+
+__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, peeringgroup => $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_filtered($c);
+ $c->response->headers(HTTP::Headers->new(
+ Allow => join(', ', @{ $allowed_methods }),
+ 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, peeringgroup => $item);
+ my $old_resource = { $item->get_inflated_columns };
+ my $resource = $self->apply_patch($c, $old_resource, $json);
+ last unless $resource;
+
+ my $form = $self->get_form($c);
+ $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, peeringgroup => $item);
+ my $resource = $self->get_valid_put_data(
+ c => $c,
+ id => $id,
+ media_type => 'application/json',
+ );
+ last unless $resource;
+ my $old_resource = { $item->get_inflated_columns };
+
+ my $form = $self->get_form($c);
+ $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 $guard = $c->model('DB')->txn_scope_guard;
+ {
+ my $item = $self->item_by_id($c, $id);
+ last unless $self->resource_exists($c, peeringgroup => $item);
+
+ foreach my $p ($item->voip_peer_hosts->all) {
+ $p->voip_peer_preferences->delete_all;
+ $p->delete;
+ }
+ $item->delete;
+ NGCP::Panel::Utils::Peering::_sip_lcr_reload(c => $c);
+ $guard->commit;
+
+ $c->response->status(HTTP_NO_CONTENT);
+ $c->response->body(q());
+ }
+ return;
+}
+
+sub end : Private {
+ my ($self, $c) = @_;
+
+ $self->log_response($c);
+}
+
+no Moose;
+1;
+
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Controller/API/PeeringRules.pm b/lib/NGCP/Panel/Controller/API/PeeringRules.pm
new file mode 100644
index 0000000000..4206aa8fcc
--- /dev/null
+++ b/lib/NGCP/Panel/Controller/API/PeeringRules.pm
@@ -0,0 +1,220 @@
+package NGCP::Panel::Controller::API::PeeringRules;
+use NGCP::Panel::Utils::Generic qw(:all);
+use Sipwise::Base;
+use Moose;
+#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 NGCP::Panel::Utils::Peering;
+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 peering groups.',
+);
+
+class_has 'query_params' => (
+ is => 'ro',
+ isa => 'ArrayRef',
+ default => sub {[
+ {
+ param => 'group_id',
+ description => 'Filter for peering rule group',
+ query => {
+ first => sub {
+ my $q = shift;
+ { group_id => $q };
+ },
+ second => sub {},
+ },
+ },
+ {
+ param => 'description',
+ description => 'Filter for peering rules description',
+ query => {
+ first => sub {
+ my $q = shift;
+ { description => { like => $q } };
+ },
+ second => sub {},
+ },
+ },
+ {
+ param => 'enabled',
+ description => 'Filter for peering rules enabled flag',
+ query => {
+ first => sub {
+ my $q = shift;
+ { enabled => $q };
+ },
+ second => sub {},
+ },
+ },
+ ]},
+);
+
+with 'NGCP::Panel::Role::API::PeeringRules';
+
+class_has('resource_name', is => 'ro', default => 'peeringrules');
+class_has('dispatch_path', is => 'ro', default => '/api/peeringrules/');
+class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-peeringrules');
+
+__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, $items) = $self->paginate_order_collection($c, $items);
+ my (@embedded, @links);
+ my $form = $self->get_form($c);
+ for my $item ($items->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_filtered($c);
+ $c->response->headers(HTTP::Headers->new(
+ Allow => join(', ', @{ $allowed_methods }),
+ 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 $guard = $c->model('DB')->txn_scope_guard;
+ {
+ my $resource = $self->get_valid_post_data(
+ c => $c,
+ media_type => 'application/json',
+ );
+ last unless $resource;
+ my $item;
+ my $form = $self->get_form($c);
+ last unless $self->validate_form(
+ c => $c,
+ resource => $resource,
+ form => $form,
+ exceptions => [qw/group_id/],
+ );
+ my $dup_item = $c->model('DB')->resultset('voip_peer_rules')->find({
+ group_id => $resource->{group_id},
+ callee_pattern => $resource->{callee_pattern},
+ caller_pattern => $resource->{caller_pattern},
+ callee_prefix => $resource->{callee_prefix},
+ });
+ if($dup_item) {
+ $c->log->error("peering rule already exists"); # TODO: user, message, trace, ...
+ $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "peering rule already exists");
+ return;
+ }
+
+ try {
+ $item = $c->model('DB')->resultset('voip_peer_rules')->create($resource);
+ NGCP::Panel::Utils::Peering::_sip_lcr_reload(c => $c);
+ } catch($e) {
+ $c->log->error("failed to create peering rule: $e"); # TODO: user, message, trace, ...
+ $self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create peering rule.");
+ last;
+ }
+
+ $guard->commit;
+
+ $c->response->status(HTTP_CREATED);
+ $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);
+}
+
+no Moose;
+1;
+
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Controller/API/PeeringRulesItem.pm b/lib/NGCP/Panel/Controller/API/PeeringRulesItem.pm
new file mode 100644
index 0000000000..75d2330997
--- /dev/null
+++ b/lib/NGCP/Panel/Controller/API/PeeringRulesItem.pm
@@ -0,0 +1,196 @@
+package NGCP::Panel::Controller::API::PeeringRulesItem;
+use NGCP::Panel::Utils::Generic qw(:all);
+use Sipwise::Base;
+use Moose;
+#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::PeeringRules';
+
+class_has('resource_name', is => 'ro', default => 'peeringrules');
+class_has('dispatch_path', is => 'ro', default => '/api/peeringrules/');
+class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-peeringrules');
+
+__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, peeringrule => $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_filtered($c);
+ $c->response->headers(HTTP::Headers->new(
+ Allow => join(', ', @{ $allowed_methods }),
+ 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, peeringrule => $item);
+ my $old_resource = { $item->get_inflated_columns };
+ my $resource = $self->apply_patch($c, $old_resource, $json);
+ last unless $resource;
+
+ my $form = $self->get_form($c);
+ $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, peeringrule => $item);
+ my $resource = $self->get_valid_put_data(
+ c => $c,
+ id => $id,
+ media_type => 'application/json',
+ );
+ last unless $resource;
+ my $old_resource = { $item->get_inflated_columns };
+
+ my $form = $self->get_form($c);
+ $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 $guard = $c->model('DB')->txn_scope_guard;
+ {
+ my $item = $self->item_by_id($c, $id);
+ last unless $self->resource_exists($c, peeringrule => $item);
+ $item->delete;
+ NGCP::Panel::Utils::Peering::_sip_lcr_reload(c => $c);
+ $guard->commit;
+
+ $c->response->status(HTTP_NO_CONTENT);
+ $c->response->body(q());
+ }
+ return;
+}
+
+sub end : Private {
+ my ($self, $c) = @_;
+
+ $self->log_response($c);
+}
+
+no Moose;
+1;
+
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Controller/API/PeeringServerPreferenceDefs.pm b/lib/NGCP/Panel/Controller/API/PeeringServerPreferenceDefs.pm
new file mode 100644
index 0000000000..b44abbeb33
--- /dev/null
+++ b/lib/NGCP/Panel/Controller/API/PeeringServerPreferenceDefs.pm
@@ -0,0 +1,107 @@
+package NGCP::Panel::Controller::API::PeeringServerPreferenceDefs;
+use NGCP::Panel::Utils::Generic qw(:all);
+use Sipwise::Base;
+use Moose;
+#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 NGCP::Panel::Utils::Preferences;
+use Path::Tiny qw(path);
+use Safe::Isa qw($_isa);
+use JSON::Types qw();
+BEGIN { extends 'Catalyst::Controller::ActionRole'; }
+require Catalyst::ActionRole::ACL;
+require Catalyst::ActionRole::CheckTrailingSlash;
+require Catalyst::ActionRole::HTTPMethods;
+require Catalyst::ActionRole::RequireSSL;
+
+with 'NGCP::Panel::Role::API';
+
+class_has('resource_name', is => 'ro', default => 'peeringserverpreferencedefs');
+class_has('dispatch_path', is => 'ro', default => '/api/peeringserverpreferencedefs/');
+class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-peeringserverpreferencedefs');
+
+__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 @links;
+ 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', $self->dispatch_path));
+
+ my $hal = Data::HAL->new(
+ links => [@links],
+ );
+ my $resource = NGCP::Panel::Utils::Preferences::api_preferences_defs( c => $c, preferences_group => 'peer_pref' );
+ $hal->resource($resource);
+
+ 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_filtered($c);
+ $c->response->headers(HTTP::Headers->new(
+ Allow => join(', ', @{ $allowed_methods }),
+ 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);
+}
+
+no Moose;
+1;
+
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Controller/API/PeeringServerPreferences.pm b/lib/NGCP/Panel/Controller/API/PeeringServerPreferences.pm
new file mode 100644
index 0000000000..d938b53b4c
--- /dev/null
+++ b/lib/NGCP/Panel/Controller/API/PeeringServerPreferences.pm
@@ -0,0 +1,131 @@
+package NGCP::Panel::Controller::API::PeeringServerPreferences;
+use NGCP::Panel::Utils::Generic qw(:all);
+use Sipwise::Base;
+use Moose;
+#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 =>
+ 'Specifies certain properties (preferences) for a Peering servers. The full list of properties can be obtained via PeeringSserverPreferenceDefs.'
+);
+
+with 'NGCP::Panel::Role::API::Preferences';
+
+class_has('resource_name', is => 'ro', default => 'peeringserverpreferences');
+class_has('dispatch_path', is => 'ro', default => '/api/peeringserverpreferences/');
+class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-peeringserverpreferences');
+
+__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 $container_type = 'peerings';
+ my $page = $c->request->params->{page} // 1;
+ my $rows = $c->request->params->{rows} // 10;
+ {
+ my $container_items = $self->item_rs($c, $container_type);
+ (my $total_count, $container_items) = $self->paginate_order_collection($c, $container_items);
+ my (@embedded, @links);
+ for my $container_item ($container_items->all) {
+ push @embedded, $self->hal_from_item($c, $container_item, $container_type);
+ push @links, Data::HAL::Link->new(
+ relation => 'ngcp:'.$self->resource_name,
+ href => sprintf('%s%d', $self->dispatch_path, $container_items->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', $self->dispatch_path, $page, $rows));
+ if(($total_count / $rows) > $page ) {
+ push @links, Data::HAL::Link->new(relation => 'next', href => sprintf('%s?page=%d&rows=%d', $self->dispatch_path, $page + 1, $rows));
+ }
+ if($page > 1) {
+ push @links, Data::HAL::Link->new(relation => 'prev', href => sprintf('%s?page=%d&rows=%d', $self->dispatch_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_filtered($c);
+ $c->response->headers(HTTP::Headers->new(
+ Allow => join(', ', @{ $allowed_methods }),
+ 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);
+}
+
+no Moose;
+1;
+
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Controller/API/PeeringServerPreferencesItem.pm b/lib/NGCP/Panel/Controller/API/PeeringServerPreferencesItem.pm
new file mode 100644
index 0000000000..629688bb06
--- /dev/null
+++ b/lib/NGCP/Panel/Controller/API/PeeringServerPreferencesItem.pm
@@ -0,0 +1,240 @@
+package NGCP::Panel::Controller::API::PeeringServerPreferencesItem;
+use NGCP::Panel::Utils::Generic qw(:all);
+use Sipwise::Base;
+use Moose;
+#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::ValidateJSON qw();
+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::HTTPMethods;
+require Catalyst::ActionRole::RequireSSL;
+
+with 'NGCP::Panel::Role::API::Preferences';
+
+class_has('resource_name', is => 'ro', default => 'peeringserverpreferences');
+class_has('dispatch_path', is => 'ro', default => '/api/peeringserverpreferences/');
+class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-peeringserverpreferences');
+
+class_has(@{ __PACKAGE__->get_journal_query_params() });
+
+__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 }),
+ @{ __PACKAGE__->get_journal_action_config(__PACKAGE__->resource_name,{
+ ACLDetachTo => '/api/root/invalid_user',
+ AllowedRole => [qw/admin reseller/],
+ Does => [qw(ACL RequireSSL)],
+ }) }
+ },
+ 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 $container_type = "peerings";
+ my $preferences_type = "peeringserverpreference";
+ my $container_item = $self->item_by_id($c, $id, $container_type);
+ last unless $self->resource_exists($c, $preferences_type => $container_item);
+
+ my $hal = $self->hal_from_item($c, $container_item, $container_type);
+
+ 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 => join(', ', @{ $allowed_methods }),
+ 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',
+ ops => [qw/add replace remove copy/],
+ );
+ last unless $json;
+
+ my $container_type = "peerings";
+ my $preferences_type = "peeringserverpreference";
+ my $container_item = $self->item_by_id($c, $id, $container_type);
+ last unless $self->resource_exists($c, $preferences_type => $container_item);
+ my $old_resource = $self->get_resource($c, $container_item, $container_type);
+ my $resource = $self->apply_patch($c, $old_resource, $json);
+ last unless $resource;
+
+ # last param is "no replace" to NOT delete existing prefs
+ # for proper PATCH behavior
+ $container_item = $self->update_item($c, $container_item, $old_resource, $resource, 0, $container_type);
+ last unless $container_item;
+
+ my $hal = $self->hal_from_item($c, $container_item, $container_type);
+ last unless $self->add_update_journal_item_hal($c,$hal);
+
+ $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, $container_item, $container_type);
+ 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 $container_type = "peerings";
+ my $preferences_type = "peeringserverpreference";
+ my $container_item = $self->item_by_id($c, $id, $container_type);
+ # TODO: systemcontact?
+ last unless $self->resource_exists($c, $preferences_type => $container_item);
+ my $resource = $self->get_valid_put_data(
+ c => $c,
+ id => $id,
+ media_type => 'application/json',
+ );
+ last unless $resource;
+ my $old_resource = $self->get_resource($c, $container_item, $container_type);
+
+ # last param is "replace" to delete all existing prefs
+ # for proper PUT behavior
+ $container_item = $self->update_item($c, $container_item, $old_resource, $resource, 1, $container_type);
+ last unless $container_item;
+
+ my $hal = $self->hal_from_item($c, $container_item, $container_type);
+ last unless $self->add_update_journal_item_hal($c,$hal);
+
+ $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, $container_item, $container_type);
+ 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 item_base_journal :Journal {
+ my $self = shift @_;
+ return $self->handle_item_base_journal(@_);
+}
+
+sub journals_get :Journal {
+ my $self = shift @_;
+ return $self->handle_journals_get(@_);
+}
+
+sub journalsitem_get :Journal {
+ my $self = shift @_;
+ return $self->handle_journalsitem_get(@_);
+}
+
+sub journals_options :Journal {
+ my $self = shift @_;
+ return $self->handle_journals_options(@_);
+}
+
+sub journalsitem_options :Journal {
+ my $self = shift @_;
+ return $self->handle_journalsitem_options(@_);
+}
+
+sub journals_head :Journal {
+ my $self = shift @_;
+ return $self->handle_journals_head(@_);
+}
+
+sub journalsitem_head :Journal {
+ my $self = shift @_;
+ return $self->handle_journalsitem_head(@_);
+}
+
+sub end : Private {
+ my ($self, $c) = @_;
+
+ $self->log_response($c);
+}
+
+no Moose;
+1;
+
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Controller/API/PeeringServers.pm b/lib/NGCP/Panel/Controller/API/PeeringServers.pm
new file mode 100644
index 0000000000..b9d08c0a7d
--- /dev/null
+++ b/lib/NGCP/Panel/Controller/API/PeeringServers.pm
@@ -0,0 +1,239 @@
+package NGCP::Panel::Controller::API::PeeringServers;
+use NGCP::Panel::Utils::Generic qw(:all);
+use Sipwise::Base;
+use Moose;
+#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 NGCP::Panel::Utils::Peering;
+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 peering groups.',
+);
+
+class_has 'query_params' => (
+ is => 'ro',
+ isa => 'ArrayRef',
+ default => sub {[
+ {
+ param => 'group_id',
+ description => 'Filter for peering server group',
+ query => {
+ first => sub {
+ my $q = shift;
+ { group_id => $q };
+ },
+ second => sub {},
+ },
+ },
+ {
+ param => 'name',
+ description => 'Filter for peering server name',
+ query => {
+ first => sub {
+ my $q = shift;
+ { name => { like => $q } };
+ },
+ second => sub {},
+ },
+ },
+ {
+ param => 'host',
+ description => 'Filter for peering server host',
+ query => {
+ first => sub {
+ my $q = shift;
+ { host => { like => $q } };
+ },
+ second => sub {},
+ },
+ },
+ {
+ param => 'ip',
+ description => 'Filter for peering server ip',
+ query => {
+ first => sub {
+ my $q = shift;
+ { host => { like => $q } };
+ },
+ second => sub {},
+ },
+ },
+ {
+ param => 'enabled',
+ description => 'Filter for peering server enabled flag',
+ query => {
+ first => sub {
+ my $q = shift;
+ { enabled => $q };
+ },
+ second => sub {},
+ },
+ }, ]},
+);
+
+with 'NGCP::Panel::Role::API::PeeringServers';
+
+class_has('resource_name', is => 'ro', default => 'peeringservers');
+class_has('dispatch_path', is => 'ro', default => '/api/peeringservers/');
+class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-peeringservers');
+
+__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, $items) = $self->paginate_order_collection($c, $items);
+ my (@embedded, @links);
+ my $form = $self->get_form($c);
+ for my $item ($items->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_filtered($c);
+ $c->response->headers(HTTP::Headers->new(
+ Allow => join(', ', @{ $allowed_methods }),
+ 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 $guard = $c->model('DB')->txn_scope_guard;
+ {
+ my $resource = $self->get_valid_post_data(
+ c => $c,
+ media_type => 'application/json',
+ );
+ last unless $resource;
+ my $item;
+
+ my $form = $self->get_form($c);
+ last unless $self->validate_form(
+ c => $c,
+ resource => $resource,
+ form => $form,
+ exceptions => [qw/group_id/],
+ );
+ my $dup_item = $c->model('DB')->resultset('voip_peer_hosts')->find({
+ name => $resource->{name},
+ });
+ if($dup_item) {
+ $c->log->error("peering server with name '$$resource{name}' already exists"); # TODO: user, message, trace, ...
+ $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "peering server with this name already exists");
+ return;
+ }
+
+ try {
+ $item = $c->model('DB')->resultset('voip_peer_hosts')->create($resource);
+ NGCP::Panel::Utils::Peering::_sip_lcr_reload(c => $c);
+ } catch($e) {
+ $c->log->error("failed to create peering server: $e"); # TODO: user, message, trace, ...
+ $self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create peering server.");
+ last;
+ }
+
+ $guard->commit;
+
+ $c->response->status(HTTP_CREATED);
+ $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);
+}
+
+no Moose;
+1;
+
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Controller/API/PeeringServersItem.pm b/lib/NGCP/Panel/Controller/API/PeeringServersItem.pm
new file mode 100644
index 0000000000..d15149493e
--- /dev/null
+++ b/lib/NGCP/Panel/Controller/API/PeeringServersItem.pm
@@ -0,0 +1,196 @@
+package NGCP::Panel::Controller::API::PeeringServersItem;
+use NGCP::Panel::Utils::Generic qw(:all);
+use Sipwise::Base;
+use Moose;
+#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::PeeringServers';
+
+class_has('resource_name', is => 'ro', default => 'peeringservers');
+class_has('dispatch_path', is => 'ro', default => '/api/peeringservers/');
+class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-peeringservers');
+
+__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, peeringserver => $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_filtered($c);
+ $c->response->headers(HTTP::Headers->new(
+ Allow => join(', ', @{ $allowed_methods }),
+ 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, peeringserver => $item);
+ my $old_resource = { $item->get_inflated_columns };
+ my $resource = $self->apply_patch($c, $old_resource, $json);
+ last unless $resource;
+
+ my $form = $self->get_form($c);
+ $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, peeringserver => $item);
+ my $resource = $self->get_valid_put_data(
+ c => $c,
+ id => $id,
+ media_type => 'application/json',
+ );
+ last unless $resource;
+ my $old_resource = { $item->get_inflated_columns };
+
+ my $form = $self->get_form($c);
+ $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 $guard = $c->model('DB')->txn_scope_guard;
+ {
+ my $item = $self->item_by_id($c, $id);
+ last unless $self->resource_exists($c, peeringserver => $item);
+ $item->delete;
+ NGCP::Panel::Utils::Peering::_sip_lcr_reload(c => $c);
+ $guard->commit;
+
+ $c->response->status(HTTP_NO_CONTENT);
+ $c->response->body(q());
+ }
+ return;
+}
+
+sub end : Private {
+ my ($self, $c) = @_;
+
+ $self->log_response($c);
+}
+
+no Moose;
+1;
+
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Controller/API/RewriteRules.pm b/lib/NGCP/Panel/Controller/API/RewriteRules.pm
index 3d49459f64..c4bc52121f 100644
--- a/lib/NGCP/Panel/Controller/API/RewriteRules.pm
+++ b/lib/NGCP/Panel/Controller/API/RewriteRules.pm
@@ -49,6 +49,17 @@ class_has 'query_params' => (
second => sub {},
},
},
+ {
+ param => 'reseller_id',
+ description => 'Filter for rules belonging to a specific reseller.',
+ query => {
+ first => sub {
+ my $q = shift;
+ return { set_id => $q };
+ },
+ second => sub {},
+ },
+ },
]},
);
diff --git a/lib/NGCP/Panel/Controller/API/Root.pm b/lib/NGCP/Panel/Controller/API/Root.pm
index b2eee61d90..dafd226637 100644
--- a/lib/NGCP/Panel/Controller/API/Root.pm
+++ b/lib/NGCP/Panel/Controller/API/Root.pm
@@ -54,6 +54,7 @@ sub GET : Allow {
"SubscriberPreferenceDefs" => 1,
"CustomerPreferenceDefs" => 1,
"ProfilePreferenceDefs" => 1,
+ "PeeringServerPreferenceDefs" => 1,
};
my @colls = $self->get_collections;
diff --git a/lib/NGCP/Panel/Controller/Peering.pm b/lib/NGCP/Panel/Controller/Peering.pm
index 533e5bdd94..659f9d472e 100644
--- a/lib/NGCP/Panel/Controller/Peering.pm
+++ b/lib/NGCP/Panel/Controller/Peering.pm
@@ -2,17 +2,16 @@ package NGCP::Panel::Controller::Peering;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
-
BEGIN { use base 'Catalyst::Controller'; }
-use NGCP::Panel::Form::PeeringGroup;
-use NGCP::Panel::Form::PeeringRule;
-use NGCP::Panel::Form::PeeringServer;
+use NGCP::Panel::Form::Peering::Group;
+use NGCP::Panel::Form::Peering::Rule;
+use NGCP::Panel::Form::Peering::Server;
use NGCP::Panel::Utils::DialogicImg;
use NGCP::Panel::Utils::Message;
use NGCP::Panel::Utils::Navigation;
use NGCP::Panel::Utils::Preferences;
-use NGCP::Panel::Utils::XMLDispatcher;
+use NGCP::Panel::Utils::Peering;
sub auto :Does(ACL) :ACLDetachTo('/denied_page') :AllowedRole(admin) {
my ($self, $c) = @_;
@@ -108,7 +107,7 @@ sub edit :Chained('base') :PathPart('edit') {
my ($self, $c) = @_;
my $posted = ($c->request->method eq 'POST');
- my $form = NGCP::Panel::Form::PeeringGroup->new;
+ my $form = NGCP::Panel::Form::Peering::Group->new;
my $params = { $c->stash->{group_result}->get_inflated_columns };
$params->{contract}{id} = delete $params->{peering_contract_id};
$params = merge($params, $c->session->{created_objects});
@@ -125,7 +124,7 @@ sub edit :Chained('base') :PathPart('edit') {
if($posted && $form->validated) {
try {
$c->stash->{group_result}->update($form->custom_get_values);
- $self->_sip_lcr_reload($c);
+ NGCP::Panel::Utils::Peering::_sip_lcr_reload(c => $c);
delete $c->session->{created_objects}->{contract};
NGCP::Panel::Utils::Message::info(
c => $c,
@@ -156,7 +155,7 @@ sub delete :Chained('base') :PathPart('delete') {
$p->delete;
}
$c->stash->{group_result}->delete;
- $self->_sip_lcr_reload($c);
+ NGCP::Panel::Utils::Peering::_sip_lcr_reload(c => $c);
NGCP::Panel::Utils::Message::info(
c => $c,
data => { $c->stash->{group_result}->get_inflated_columns },
@@ -177,7 +176,7 @@ sub create :Chained('group_list') :PathPart('create') :Args(0) {
my ($self, $c) = @_;
my $posted = ($c->request->method eq 'POST');
- my $form = NGCP::Panel::Form::PeeringGroup->new;
+ my $form = NGCP::Panel::Form::Peering::Group->new;
my $params = {};
$params = merge($params, $c->session->{created_objects});
$form->process(
@@ -195,7 +194,7 @@ sub create :Chained('group_list') :PathPart('create') :Args(0) {
try {
$c->model('DB')->resultset('voip_peer_groups')->create(
$formdata );
- $self->_sip_lcr_reload($c);
+ NGCP::Panel::Utils::Peering::_sip_lcr_reload(c => $c);
delete $c->session->{created_objects}->{contract};
NGCP::Panel::Utils::Message::info(
c => $c,
@@ -242,7 +241,7 @@ sub servers_create :Chained('servers_list') :PathPart('create') :Args(0) {
my ($self, $c) = @_;
my $posted = ($c->request->method eq 'POST');
- my $form = NGCP::Panel::Form::PeeringServer->new(ctx => $c);
+ my $form = NGCP::Panel::Form::Peering::Server->new(ctx => $c);
$form->process(
posted => $posted,
params => $c->request->params,
@@ -266,7 +265,7 @@ sub servers_create :Chained('servers_list') :PathPart('create') :Args(0) {
enabled => $form->values->{enabled},
};
my $server = $c->stash->{group_result}->voip_peer_hosts->create($dbvalues);
- $self->_sip_lcr_reload($c);
+ NGCP::Panel::Utils::Peering::_sip_lcr_reload(c => $c);
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Peering server successfully created'),
@@ -323,7 +322,7 @@ sub servers_edit :Chained('servers_base') :PathPart('edit') :Args(0) {
my ($self, $c) = @_;
my $posted = ($c->request->method eq 'POST');
- my $form = NGCP::Panel::Form::PeeringServer->new(ctx => $c);
+ my $form = NGCP::Panel::Form::Peering::Server->new(ctx => $c);
$form->process(
posted => $posted,
params => $c->request->params,
@@ -338,7 +337,7 @@ sub servers_edit :Chained('servers_base') :PathPart('edit') :Args(0) {
if($posted && $form->validated) {
try {
$c->stash->{server_result}->update($form->values);
- $self->_sip_lcr_reload($c);
+ NGCP::Panel::Utils::Peering::_sip_lcr_reload(c => $c);
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Peering server successfully updated'),
@@ -366,7 +365,7 @@ sub servers_delete :Chained('servers_base') :PathPart('delete') :Args(0) {
try {
$c->stash->{server_result}->delete;
- $self->_sip_lcr_reload($c);
+ NGCP::Panel::Utils::Peering::_sip_lcr_reload(c => $c);
NGCP::Panel::Utils::Message::info(
c => $c,
data => { $c->stash->{server_result}->get_inflated_columns },
@@ -567,7 +566,7 @@ sub rules_create :Chained('rules_list') :PathPart('create') :Args(0) {
my ($self, $c) = @_;
my $posted = ($c->request->method eq 'POST');
- my $form = NGCP::Panel::Form::PeeringRule->new;
+ my $form = NGCP::Panel::Form::Peering::Rule->new;
$form->process(
posted => $posted,
params => $c->request->params,
@@ -582,7 +581,7 @@ sub rules_create :Chained('rules_list') :PathPart('create') :Args(0) {
try {
$form->values->{callee_prefix} //= '';
$c->stash->{group_result}->voip_peer_rules->create($form->values);
- $self->_sip_lcr_reload($c);
+ NGCP::Panel::Utils::Peering::_sip_lcr_reload(c => $c);
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Peering rule successfully created'),
@@ -639,7 +638,7 @@ sub rules_edit :Chained('rules_base') :PathPart('edit') :Args(0) {
my ($self, $c) = @_;
my $posted = ($c->request->method eq 'POST');
- my $form = NGCP::Panel::Form::PeeringRule->new;
+ my $form = NGCP::Panel::Form::Peering::Rule->new;
$form->process(
posted => $posted,
params => $c->request->params,
@@ -655,7 +654,7 @@ sub rules_edit :Chained('rules_base') :PathPart('edit') :Args(0) {
try {
$form->values->{callee_prefix} //= '';
$c->stash->{rule_result}->update($form->values);
- $self->_sip_lcr_reload($c);
+ NGCP::Panel::Utils::Peering::_sip_lcr_reload(c => $c);
NGCP::Panel::Utils::Message::info(
c => $c,
desc => $c->loc('Peering rule successfully changed'),
@@ -683,7 +682,7 @@ sub rules_delete :Chained('rules_base') :PathPart('delete') :Args(0) {
try {
$c->stash->{rule_result}->delete;
- $self->_sip_lcr_reload($c);
+ NGCP::Panel::Utils::Peering::_sip_lcr_reload(c => $c);
NGCP::Panel::Utils::Message::info(
c => $c,
data => { $c->stash->{rule_result}->get_inflated_columns },
@@ -700,19 +699,6 @@ sub rules_delete :Chained('rules_base') :PathPart('delete') :Args(0) {
return;
}
-sub _sip_lcr_reload {
- my ($self, $c) = @_;
- my $dispatcher = NGCP::Panel::Utils::XMLDispatcher->new;
- $dispatcher->dispatch($c, "proxy-ng", 1, 1, <
-
-lcr.reload
-
-
-EOF
-
- return 1;
-}
__PACKAGE__->meta->make_immutable;
@@ -841,12 +827,6 @@ Show a modal to edit a peering rule.
Delete a peering rule.
-=head2 _sip_lcr_reload
-
-This is ported from ossbss.
-
-Reloads lcr cache of sip proxies.
-
=head1 AUTHOR
Gerhard Jungwirth C<< >>
diff --git a/lib/NGCP/Panel/Form/PeeringGroup.pm b/lib/NGCP/Panel/Form/Peering/Group.pm
similarity index 96%
rename from lib/NGCP/Panel/Form/PeeringGroup.pm
rename to lib/NGCP/Panel/Form/Peering/Group.pm
index 4e46c1bb98..ec5c5654f7 100644
--- a/lib/NGCP/Panel/Form/PeeringGroup.pm
+++ b/lib/NGCP/Panel/Form/Peering/Group.pm
@@ -1,4 +1,4 @@
-package NGCP::Panel::Form::PeeringGroup;
+package NGCP::Panel::Form::Peering::Group;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
@@ -72,7 +72,7 @@ sub custom_get_values {
=head1 NAME
-NGCP::Panel::Form::PeeringGroup
+NGCP::Panel::Form::Peering::Group
=head1 DESCRIPTION
diff --git a/lib/NGCP/Panel/Form/PeeringRule.pm b/lib/NGCP/Panel/Form/Peering/Rule.pm
similarity index 96%
rename from lib/NGCP/Panel/Form/PeeringRule.pm
rename to lib/NGCP/Panel/Form/Peering/Rule.pm
index 3895d0d255..0846566fae 100644
--- a/lib/NGCP/Panel/Form/PeeringRule.pm
+++ b/lib/NGCP/Panel/Form/Peering/Rule.pm
@@ -1,4 +1,4 @@
-package NGCP::Panel::Form::PeeringRule;
+package NGCP::Panel::Form::Peering::Rule;
use Sipwise::Base;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
@@ -81,7 +81,7 @@ __END__
=head1 NAME
-NGCP::Panel::Form::PeeringRule
+NGCP::Panel::Form::Peering::Rule
=head1 DESCRIPTION
diff --git a/lib/NGCP/Panel/Form/Peering/RuleAPI.pm b/lib/NGCP/Panel/Form/Peering/RuleAPI.pm
new file mode 100644
index 0000000000..70728ab95f
--- /dev/null
+++ b/lib/NGCP/Panel/Form/Peering/RuleAPI.pm
@@ -0,0 +1,45 @@
+package NGCP::Panel::Form::Peering::RuleAPI;
+
+use HTML::FormHandler::Moose;
+extends 'NGCP::Panel::Form::Peering::Rule';
+
+has_field 'group_id' => (
+ type => 'PosInteger',
+ required => 1,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The peering group this rule belongs to.']
+ },
+);
+
+has_block 'fields' => (
+ tag => 'div',
+ class => [qw/modal-body/],
+ render_list => [qw/group_id callee_prefix callee_pattern caller_pattern description enabled/],
+);
+
+1;
+__END__
+
+=head1 NAME
+
+NGCP::Panel::Form::Peering::RuleAPI
+
+=head1 DESCRIPTION
+
+-
+
+=head1 METHODS
+
+=head1 AUTHOR
+
+Irina Peshinskaya
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Form/PeeringServer.pm b/lib/NGCP/Panel/Form/Peering/Server.pm
similarity index 94%
rename from lib/NGCP/Panel/Form/PeeringServer.pm
rename to lib/NGCP/Panel/Form/Peering/Server.pm
index f7d3a60301..0d69047fa9 100644
--- a/lib/NGCP/Panel/Form/PeeringServer.pm
+++ b/lib/NGCP/Panel/Form/Peering/Server.pm
@@ -1,4 +1,4 @@
-package NGCP::Panel::Form::PeeringServer;
+package NGCP::Panel::Form::Peering::Server;
use Sipwise::Base;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
@@ -125,14 +125,19 @@ sub validate_via_route {
$field->add_error("Invalid SIP URI, must be (comma-separated) SIP URI(s) in form sip:ip:port");
}
}
-
+#sub validate {
+# my ($self) = @_;
+# my $c = $self->ctx;
+# return unless $c;
+# my $model = $c->
+#}
1;
__END__
=head1 NAME
-NGCP::Panel::Form::PeeringServer
+NGCP::Panel::Form::Peering::Server
=head1 DESCRIPTION
diff --git a/lib/NGCP/Panel/Form/Peering/ServerAPI.pm b/lib/NGCP/Panel/Form/Peering/ServerAPI.pm
new file mode 100644
index 0000000000..36b2caa31c
--- /dev/null
+++ b/lib/NGCP/Panel/Form/Peering/ServerAPI.pm
@@ -0,0 +1,44 @@
+package NGCP::Panel::Form::Peering::ServerAPI;
+use HTML::FormHandler::Moose;
+extends 'NGCP::Panel::Form::Peering::Server';
+
+has_field 'group_id' => (
+ type => 'PosInteger',
+ required => 1,
+ element_attr => {
+ rel => ['tooltip'],
+ title => ['The peering group this server belongs to.']
+ },
+);
+
+has_block 'fields' => (
+ tag => 'div',
+ class => [qw/modal-body/],
+ render_list => [qw/group_id name ip host port transport weight via_route enabled/],
+);
+
+1;
+__END__
+
+=head1 NAME
+
+NGCP::Panel::Form::Peering::ServerAPI
+
+=head1 DESCRIPTION
+
+-
+
+=head1 METHODS
+
+=head1 AUTHOR
+
+Irina Peshinskaya
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Role/API/PeeringGroups.pm b/lib/NGCP/Panel/Role/API/PeeringGroups.pm
new file mode 100644
index 0000000000..b7ae950e12
--- /dev/null
+++ b/lib/NGCP/Panel/Role/API/PeeringGroups.pm
@@ -0,0 +1,95 @@
+package NGCP::Panel::Role::API::PeeringGroups;
+use NGCP::Panel::Utils::Generic qw(:all);
+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::Peering::Group;
+use NGCP::Panel::Utils::Peering;
+
+sub item_rs {
+ my ($self, $c) = @_;
+ my $item_rs = $c->model('DB')->resultset('voip_peer_groups');
+ return $item_rs;
+}
+
+sub get_form {
+ my ($self, $c) = @_;
+ return NGCP::Panel::Form::Peering::Group->new;
+}
+
+sub hal_from_item {
+ my ($self, $c, $item, $form) = @_;
+ my %resource = $item->get_inflated_columns;
+ $resource{contract_id} = delete $resource{peering_contract_id};
+ 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)),
+ ],
+ relation => 'ngcp:'.$self->resource_name,
+ );
+
+ $form //= $self->get_form($c);
+
+ $self->validate_form(
+ c => $c,
+ resource => \%resource,
+ form => $form,
+ run => 0,
+ );
+
+ $resource{id} = int($item->id);
+ $hal->resource({%resource});
+ return $hal;
+}
+
+sub item_by_id {
+ my ($self, $c, $id) = @_;
+ my $item_rs = $self->item_rs($c);
+ return $item_rs->find($id);
+}
+
+sub update_item {
+ my ($self, $c, $item, $old_resource, $resource, $form) = @_;
+
+ $form //= $self->get_form($c);
+ return unless $self->validate_form(
+ c => $c,
+ form => $form,
+ resource => $resource,
+ );
+ $resource = $form->custom_get_values;
+ last unless $resource;
+
+ my $dup_item = $c->model('DB')->resultset('voip_peer_groups')->find({
+ name => $resource->{name},
+ });
+ if($dup_item && $dup_item->id != $item->id) {
+ $c->log->error("peering group with name '$$resource{name}' already exists"); # TODO: user, message, trace, ...
+ $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "peering group with this name already exists");
+ return;
+ }
+
+ $item->update($resource);
+ NGCP::Panel::Utils::Peering::_sip_lcr_reload(c => $c);
+ return $item;
+}
+
+1;
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Role/API/PeeringRules.pm b/lib/NGCP/Panel/Role/API/PeeringRules.pm
new file mode 100644
index 0000000000..fb14074788
--- /dev/null
+++ b/lib/NGCP/Panel/Role/API/PeeringRules.pm
@@ -0,0 +1,98 @@
+package NGCP::Panel::Role::API::PeeringRules;
+use NGCP::Panel::Utils::Generic qw(:all);
+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::Peering::RuleAPI;
+use NGCP::Panel::Utils::Peering;
+
+sub item_rs {
+ my ($self, $c) = @_;
+ my $item_rs = $c->model('DB')->resultset('voip_peer_rules');
+ return $item_rs;
+}
+
+sub get_form {
+ my ($self, $c) = @_;
+ return NGCP::Panel::Form::Peering::RuleAPI->new;
+}
+
+sub hal_from_item {
+ my ($self, $c, $item, $form) = @_;
+ my %resource = $item->get_inflated_columns;
+
+ 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:peeringgroups', href => sprintf("/api/peeringgroups/%d", $resource{group_id})),
+ ],
+ relation => 'ngcp:'.$self->resource_name,
+ );
+
+ $form //= $self->get_form($c);
+
+ $self->validate_form(
+ c => $c,
+ resource => \%resource,
+ form => $form,
+ exceptions => [qw/group_id/],
+ run => 0,
+ );
+
+ $resource{id} = int($item->id);
+ $hal->resource({%resource});
+ return $hal;
+}
+
+sub item_by_id {
+ my ($self, $c, $id) = @_;
+ my $item_rs = $self->item_rs($c);
+ return $item_rs->find($id);
+}
+
+sub update_item {
+ my ($self, $c, $item, $old_resource, $resource, $form) = @_;
+
+ $form //= $self->get_form($c);
+ return unless $self->validate_form(
+ c => $c,
+ form => $form,
+ resource => $resource,
+ exceptions => [qw/group_id/],
+ );
+ my $dup_item = $c->model('DB')->resultset('voip_peer_rules')->find({
+ group_id => $resource->{group_id},
+ callee_pattern => $resource->{callee_pattern},
+ caller_pattern => $resource->{caller_pattern},
+ callee_prefix => $resource->{callee_prefix},
+ });
+ if($dup_item && $dup_item->id != $item->id) {
+ $c->log->error("peering rule already exists"); # TODO: user, message, trace, ...
+ $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "peering rule already exists");
+ return;
+ }
+
+ $item->update($resource);
+ NGCP::Panel::Utils::Peering::_sip_lcr_reload(c => $c);
+ return $item;
+}
+
+1;
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Role/API/PeeringServers.pm b/lib/NGCP/Panel/Role/API/PeeringServers.pm
new file mode 100644
index 0000000000..db79147e99
--- /dev/null
+++ b/lib/NGCP/Panel/Role/API/PeeringServers.pm
@@ -0,0 +1,96 @@
+package NGCP::Panel::Role::API::PeeringServers;
+use NGCP::Panel::Utils::Generic qw(:all);
+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::Peering::ServerAPI;
+use NGCP::Panel::Utils::Peering;
+
+sub item_rs {
+ my ($self, $c) = @_;
+ my $item_rs = $c->model('DB')->resultset('voip_peer_hosts');
+ return $item_rs;
+}
+
+sub get_form {
+ my ($self, $c) = @_;
+ return NGCP::Panel::Form::Peering::ServerAPI->new;
+}
+
+sub hal_from_item {
+ my ($self, $c, $item, $form) = @_;
+ my %resource = $item->get_inflated_columns;
+
+ 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:peeringgroups', href => sprintf("/api/peeringgroups/%d", $resource{group_id})),
+ ],
+ relation => 'ngcp:'.$self->resource_name,
+ );
+
+ $form //= $self->get_form($c);
+
+ $self->validate_form(
+ c => $c,
+ resource => \%resource,
+ form => $form,
+ run => 0,
+ exceptions => [qw/group_id/],
+ );
+
+ $resource{id} = int($item->id);
+ $hal->resource({%resource});
+ return $hal;
+}
+
+sub item_by_id {
+ my ($self, $c, $id) = @_;
+ my $item_rs = $self->item_rs($c);
+ return $item_rs->find($id);
+}
+
+sub update_item {
+ my ($self, $c, $item, $old_resource, $resource, $form) = @_;
+
+ $form //= $self->get_form($c);
+ return unless $self->validate_form(
+ c => $c,
+ form => $form,
+ resource => $resource,
+ exceptions => [qw/group_id/],
+ );
+
+ my $dup_item = $c->model('DB')->resultset('voip_peer_hosts')->find({
+ name => $resource->{name},
+ });
+ if($dup_item && $dup_item->id != $item->id) {
+ $c->log->error("peering server with name '$$resource{name}' already exists"); # TODO: user, message, trace, ...
+ $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "peering server with this name already exists");
+ return;
+ }
+
+ $item->update($resource);
+ NGCP::Panel::Utils::Peering::_sip_lcr_reload(c => $c);
+ return $item;
+}
+
+1;
+# vim: set tabstop=4 expandtab:
diff --git a/lib/NGCP/Panel/Utils/Peering.pm b/lib/NGCP/Panel/Utils/Peering.pm
new file mode 100644
index 0000000000..a951fb4d17
--- /dev/null
+++ b/lib/NGCP/Panel/Utils/Peering.pm
@@ -0,0 +1,50 @@
+package NGCP::Panel::Utils::Peering;
+use NGCP::Panel::Utils::XMLDispatcher;
+
+use strict;
+use warnings;
+
+sub _sip_lcr_reload {
+ my(%params) = @_;
+ my($c) = @params{qw/c/};
+ my $dispatcher = NGCP::Panel::Utils::XMLDispatcher->new;
+ $dispatcher->dispatch($c, "proxy-ng", 1, 1, <
+
+lcr.reload
+
+
+EOF
+
+ return 1;
+}
+
+1;
+
+=head1 NAME
+
+NGCP::Panel::Utils::Peering
+
+=head1 DESCRIPTION
+
+A temporary helper to manipulate peerings related data
+
+=head1 METHODS
+
+=head2 _sip_lcr_reload
+
+This is ported from ossbss.
+
+Reloads lcr cache of sip proxies.
+
+=head1 AUTHOR
+
+Irina Peshinskaya
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+# vim: set tabstop=4 expandtab:
diff --git a/t/api-rest/api-ncoslevels.t b/t/api-rest/api-ncoslevels.t
new file mode 100644
index 0000000000..1ff6718a94
--- /dev/null
+++ b/t/api-rest/api-ncoslevels.t
@@ -0,0 +1,41 @@
+use strict;
+use warnings;
+
+use Test::Collection;
+use Test::FakeData;
+use Test::More;
+use Data::Dumper;
+
+#init test_machine
+my $test_machine = Test::Collection->new(
+ name => 'ncoslevels',
+);
+$test_machine->methods->{collection}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS POST)};
+$test_machine->methods->{item}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS PUT PATCH DELETE)};
+
+my $fake_data = Test::FakeData->new;
+$fake_data->set_data_from_script({
+ ncoslevels => {
+ data => {
+ reseller_id => sub { return shift->get_id('resellers',@_); },
+ level => 'api_test ncos '.time(),
+ mode => 'whitelist',#blacklist
+ description => 'api_test ncos level description',
+ local_ac => '1',#out
+ },
+ },
+});
+
+#for item creation test purposes /post request data/
+$test_machine->DATA_ITEM_STORE($fake_data->process('ncoslevels'));
+
+$test_machine->form_data_item( );
+# create 3 new field pbx devices from DATA_ITEM
+$test_machine->check_create_correct( 3, sub{ $_[0]->{level} .= $_[1]->{i}; } );
+$test_machine->check_get2put();
+$test_machine->check_bundle();
+$test_machine->clear_test_data_all();
+
+done_testing;
+
+# vim: set tabstop=4 expandtab:
diff --git a/t/api-rest/api-peeringgroups.t b/t/api-rest/api-peeringgroups.t
new file mode 100644
index 0000000000..3e02d8af3d
--- /dev/null
+++ b/t/api-rest/api-peeringgroups.t
@@ -0,0 +1,47 @@
+use strict;
+use warnings;
+
+use Test::Collection;
+use Test::FakeData;
+use Test::More;
+use Data::Dumper;
+
+#init test_machine
+my $test_machine = Test::Collection->new(
+ name => 'peeringgroups',
+);
+$test_machine->methods->{collection}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS POST)};
+$test_machine->methods->{item}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS PUT PATCH DELETE)};
+
+my $fake_data = Test::FakeData->new;
+$fake_data->set_data_from_script({
+ peeringgroups => {
+ data => {
+ name => 'test_api peering group',
+ priority => '1',
+ description => 'test_api peering group',
+ contract_id => sub { return shift->get_id('contracts',@_); },,
+ },
+ query => ['name'],
+ },
+});
+
+#for item creation test purposes /post request data/
+$test_machine->DATA_ITEM_STORE($fake_data->process('peeringgroups'));
+
+$test_machine->form_data_item( );
+# create 3 new field pbx devices from DATA_ITEM
+$test_machine->check_create_correct( 3, sub{ $_[0]->{name} .= $_[1]->{i}; } );
+{
+ my $data = $test_machine->DATA_ITEM;
+ $data->{name} .= 1;
+ my ($res,$result_item,$req) = $test_machine->request_post($data);
+ $test_machine->http_code_msg(422, "POST same peering group code again", $res, $result_item);
+}
+$test_machine->check_get2put();
+$test_machine->check_bundle();
+$test_machine->clear_test_data_all();
+
+done_testing;
+
+# vim: set tabstop=4 expandtab:
diff --git a/t/api-rest/api-peeringrules.t b/t/api-rest/api-peeringrules.t
new file mode 100644
index 0000000000..7c7a785b8a
--- /dev/null
+++ b/t/api-rest/api-peeringrules.t
@@ -0,0 +1,49 @@
+use strict;
+use warnings;
+
+use Test::Collection;
+use Test::FakeData;
+use Test::More;
+use Data::Dumper;
+
+#init test_machine
+my $test_machine = Test::Collection->new(
+ name => 'peeringrules',
+ embedded_resources => [qw/peeringgroups/]
+);
+$test_machine->methods->{collection}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS POST)};
+$test_machine->methods->{item}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS PUT PATCH DELETE)};
+
+my $fake_data = Test::FakeData->new;
+$fake_data->set_data_from_script({
+ peeringrules => {
+ data => {
+ group_id => sub { return shift->get_id('peeringgroups',@_); },
+ callee_prefix => '333',
+ callee_pattern => '^111$',
+ caller_pattern => '^222$',
+ description => 'api_test peering rule',
+ enabled => '1',
+ }
+ },
+});
+
+#for item creation test purposes /post request data/
+$test_machine->DATA_ITEM_STORE($fake_data->process('peeringrules'));
+
+$test_machine->form_data_item( );
+# create 3 new field pbx devices from DATA_ITEM
+$test_machine->check_create_correct( 3, sub{ $_[0]->{callee_prefix} .= $_[1]->{i}; } );
+{
+ my $data = $test_machine->DATA_ITEM;
+ $data->{callee_prefix} .= 1;
+ my ($res,$result_item,$req) = $test_machine->request_post($data);
+ $test_machine->http_code_msg(422, "POST same peering rule code again", $res, $result_item);
+}
+$test_machine->check_get2put();
+$test_machine->check_bundle();
+$test_machine->clear_test_data_all();
+
+done_testing;
+
+# vim: set tabstop=4 expandtab:
diff --git a/t/api-rest/api-peeringservers.t b/t/api-rest/api-peeringservers.t
new file mode 100644
index 0000000000..ef2921a7d0
--- /dev/null
+++ b/t/api-rest/api-peeringservers.t
@@ -0,0 +1,54 @@
+use strict;
+use warnings;
+
+use Test::Collection;
+use Test::FakeData;
+use Test::More;
+use Data::Dumper;
+
+#init test_machine
+my $test_machine = Test::Collection->new(
+ name => 'peeringservers',
+ embedded_resources => [qw/peeringgroups/]
+);
+$test_machine->methods->{collection}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS POST)};
+$test_machine->methods->{item}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS PUT PATCH DELETE)};
+
+my $fake_data = Test::FakeData->new;
+$fake_data->set_data_from_script({
+ peeringservers => {
+ data => {
+ group_id => sub { return shift->get_id('peeringgroups',@_); },
+ name => 'test_api peering host',
+ ip => '1.1.1.1',
+ host => 'test-api.com',
+ port => '1025',
+ transport => '1',
+ weight => '1',
+ via_route => '',
+ via_lb => '',
+ enabled => '1',
+ },
+ query => ['group_id','name'],
+ },
+});
+
+#for item creation test purposes /post request data/
+$test_machine->DATA_ITEM_STORE($fake_data->process('peeringservers'));
+
+$test_machine->form_data_item( );
+# create 3 new field pbx devices from DATA_ITEM
+$test_machine->check_create_correct( 3, sub{ $_[0]->{name} .= $_[1]->{i}; } );
+{
+ my $data = $test_machine->DATA_ITEM;
+ $data->{name} .= 1;
+ my ($res,$result_item,$req) = $test_machine->request_post($data);
+ $test_machine->http_code_msg(422, "POST same peering server name again", $res, $result_item);
+}
+$test_machine->check_get2put();
+$test_machine->check_bundle();
+$test_machine->clear_test_data_all();
+
+done_testing;
+
+# vim: set tabstop=4 expandtab:
diff --git a/t/api-rest/api-preferences.t b/t/api-rest/api-preferences.t
new file mode 100644
index 0000000000..00c7360aee
--- /dev/null
+++ b/t/api-rest/api-preferences.t
@@ -0,0 +1,139 @@
+use strict;
+use warnings;
+
+use Test::Collection;
+use Test::FakeData;
+use Test::More;
+use Data::Dumper;
+use JSON;
+use Clone qw/clone/;
+use feature "state";
+
+
+#init test_machine
+my $test_machine = Test::Collection->new(
+ name => 'preferences',
+);
+my $fake_data = Test::FakeData->new;
+$fake_data->set_data_from_script({
+ preferences => {
+ data => {
+ peeringserver_id => sub { return shift->get_id('peeringservers',@_); },
+ customer_id => sub { return shift->get_id('customers',@_); },
+ subscriber_id => sub { return shift->get_id('subscribers',@_); },
+ domain_id => sub { return shift->get_id('domains',@_); },
+ profile_id => sub { return shift->get_id('subscriberprofiles',@_); },
+
+ rewriteruleset_id => sub { return shift->get_id('rewriterulesets',@_); },
+ soundset_id => sub { return shift->get_id('soundsets',@_); },
+ ncoslevel_id => sub { return shift->get_id('ncoslevels',@_); },
+ },
+ },
+});
+
+#for item creation test purposes /post request data/
+$test_machine->DATA_ITEM_STORE($fake_data->process('preferences'));
+$test_machine->form_data_item( );
+
+my @apis = qw/subscriber domain peeringserver customer profile/;
+#my @apis = qw/peeringserver/;
+
+foreach my $api (@apis){
+ my $preferences_old;
+ my $preferences_put;
+ my $index = $api.'_id';
+ my ($preferences) = {'uri' => '/api/'.$api.'preferences/'.$test_machine->DATA_ITEM->{$index}};
+ (undef, $preferences_old) = $test_machine->check_item_get($preferences->{uri});
+ #$preferences->{content} = $preferences_old;
+
+ my $api_test_machine = Test::Collection->new(
+ name => $api.'preferences',
+ );
+ $api_test_machine->methods->{collection}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS POST)};
+ $api_test_machine->methods->{item}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS PUT PATCH DELETE)};
+ my $defs = $api_test_machine->get_item_hal($api.'preferencedefs');
+ delete $defs->{content}->{_links};
+ foreach my $preference_name(keys %{$defs->{content}}){
+ my $preference = $defs->{content}->{$preference_name};
+ $preference->{name} = $preference_name;
+ #if($preference->{read_only}){
+ # next;
+ #}
+ my $value;
+ if('boolean' eq $preference->{data_type}){
+ $value = JSON::true;
+ }elsif('enum' eq $preference->{data_type}){
+ my @values = @{$preference->{enum_values}};
+ if(@values){
+ if($#values > 0){
+ #take second value from enum if exists
+ $value = $values[1]->{value};
+ }
+ }
+ #foreach my $preference_enum_value(@{$preference->{enum_values}}){
+ #
+ #}
+ }elsif('string' eq $preference->{data_type}){
+ $value = get_preference_existen_value($preference) // "test_api preference string";
+ }elsif('int' eq $preference->{data_type}){
+ $value = get_preference_existen_value($preference) // 33;
+ }else{
+ die("unknown data type: ".$preference->{data_type}." for $preference_name;\n");
+ }
+ if($value && 'no_process' ne $value){
+ if($preference->{max_occur} > 0 ){
+ $preferences->{content}->{$preference_name} = 1 < $preference->{max_occur} ? [$value] : $value ;
+ }
+ }else{
+ #print "Undefined value for preference: $api:$preference_name;\n";
+ #print Dumper $preference;
+ }
+ }
+ #(undef, $preferences_put->{content}) = $test_machine->request_put($preferences->{content},$preferences->{uri});
+ #we don't check read_only flag when update preferences?
+ (undef, $preferences_put->{content}) = $test_machine->check_put2get({data_in=>$preferences->{content},uri=>$preferences->{uri}},undef, 1);
+ (undef, $preferences_put->{content}) = $test_machine->request_put($preferences_old,$preferences->{uri});
+}
+
+done_testing;
+
+
+#----------------- aux
+sub get_preference_existen_value{
+ my $preference = shift;
+ my $res;
+ if($preference->{name}=~/^rewrite_rule_set$/){
+ $res = $fake_data->{data}->{rewriterulesets}->{data}->{name};
+ }elsif($preference->{name}=~/^(adm_)?ncos$/){
+ $res = $fake_data->{data}->{ncoslevels}->{data}->{level};
+ }elsif($preference->{name}=~/^(contract_)?sound_set$/){
+ $res = $fake_data->{data}->{soundsets}->{data}->{name};
+ }elsif($preference->{name}=~/^(man_)?allowed_ips_grp$/){
+ $res= 'no_process';
+ }
+ return $res;
+}
+
+__DATA__
+
+'lbrtp_set' => {
+ 'enum_values' => [
+ {
+ 'value' => undef,
+ 'default_val' => $VAR1->{'mobile_push_expiry'}{'read_only'},
+ 'label' => 'None'
+ },
+ {
+ 'value' => '50',
+ 'default_val' => $VAR1->{'mobile_push_expiry'}{'read_only'},
+ 'label' => 'default'
+ }
+ ],
+ 'data_type' => 'enum',
+ 'read_only' => $VAR1->{'mobile_push_expiry'}{'read_only'},
+ 'max_occur' => 1,
+ 'label' => 'The cluster set used for SIP lb and RTP',
+ 'description' => 'Use a particular cluster set of load-balancers for SIP towards this endpoint (only for peers, as for subscribers it is defined by Path during registration) and of RTP relays (both peers and subscribers).'
+},
+
+# vim: set tabstop=4 expandtab:
diff --git a/t/api-rest/api-root.t b/t/api-rest/api-root.t
index aeba0f2a40..9fa5d15c0c 100644
--- a/t/api-rest/api-root.t
+++ b/t/api-rest/api-root.t
@@ -88,6 +88,11 @@ $ua->credentials($netloc, "api_admin_http", $user, $pass);
pbxdevicemodelimages => 1,
pbxdeviceprofiles => 1,
pbxdevices => 1,
+ peeringgroups => 1,
+ peeringrules => 1,
+ peeringserverpreferences => 1,
+ peeringserverpreferencedefs => 1,
+ peeringservers => 1,
profilepackages => 1,
profilepreferences => 1,
profilepreferencedefs => 1,
diff --git a/t/api-rest/api-soundsets.t b/t/api-rest/api-soundsets.t
new file mode 100644
index 0000000000..c2de42c867
--- /dev/null
+++ b/t/api-rest/api-soundsets.t
@@ -0,0 +1,41 @@
+use strict;
+use warnings;
+
+use Test::Collection;
+use Test::FakeData;
+use Test::More;
+use Data::Dumper;
+
+#init test_machine
+my $test_machine = Test::Collection->new(
+ name => 'soundsets',
+);
+$test_machine->methods->{collection}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS POST)};
+$test_machine->methods->{item}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS PUT PATCH DELETE)};
+
+my $fake_data = Test::FakeData->new;
+$fake_data->set_data_from_script({
+ soundsets => {
+ data => {
+ reseller_id => sub { return shift->get_id('resellers',@_); },
+ contract_id => sub { return shift->get_id('customers',@_); },
+ name => 'api_test soundset name'.time(),
+ description => 'api_test soundset description',
+ contract_default => '1',#0
+ },
+ },
+});
+
+#for item creation test purposes /post request data/
+$test_machine->DATA_ITEM_STORE($fake_data->process('soundsets'));
+
+$test_machine->form_data_item( );
+# create 3 new sound sets from DATA_ITEM
+$test_machine->check_create_correct( 3, sub{ $_[0]->{name} .= $_[1]->{i}; } );
+$test_machine->check_get2put();
+$test_machine->check_bundle();
+$test_machine->clear_test_data_all();
+
+done_testing;
+
+# vim: set tabstop=4 expandtab:
diff --git a/t/lib/Test/Collection.pm b/t/lib/Test/Collection.pm
index e3650660d1..33dd39d7c2 100644
--- a/t/lib/Test/Collection.pm
+++ b/t/lib/Test/Collection.pm
@@ -331,9 +331,13 @@ sub get_hal_from_collection{
if(ref $list_collection->{_links}->{$hal_name} eq "HASH") {
$reshal = $list_collection;
$location = $reshal->{_links}->{$hal_name}->{href};
- } else {
+ } elsif( $list_collection->{_embedded} && ref $list_collection->{_embedded}->{$hal_name} eq 'ARRAY') {
$reshal = $list_collection->{_embedded}->{$hal_name}->[0];
$location = $reshal->{_links}->{self}->{href};
+ }elsif( ref $list_collection eq 'HASH' && $list_collection->{_links}->{self}->{href}) {
+#preferencedefs collection
+ $reshal = $list_collection;
+ $location = $reshal->{_links}->{self}->{href};
}
return ($reshal,$location);
}
@@ -886,7 +890,7 @@ sub check_get2put{
}
sub check_put2get{
- my($self, $put_in, $get_in, $nocheck) = @_;
+ my($self, $put_in, $get_in, $check_cb_or_switch) = @_;
my($put_out,$get_out);
@@ -903,8 +907,11 @@ sub check_put2get{
delete $get_out->{content}->{_links};
delete $get_out->{content}->{_embedded};
my $item_id = delete $get_out->{content}->{id};
- if(!$nocheck){
- is_deeply($put_out->{content_in}, $get_out->{content}, "check_put2get: check PUTed item against POSTed item");
+ if('CODE' eq ref $check_cb_or_switch){
+ $check_cb_or_switch->($put_out,$get_out);
+ }
+ if(!$check_cb_or_switch || 'CODE' eq ref $check_cb_or_switch){
+ is_deeply($put_out->{content_in}, $get_out->{content}, "check_put2get: check PUTed item against GETed item");
}
$get_out->{content}->{id} = $item_id;
return ($put_out,$get_out);