From e8fa8b2f9c1ad042a71dda5d356a16480accbe32 Mon Sep 17 00:00:00 2001 From: Andreas Granig Date: Tue, 17 Dec 2013 14:56:14 +0100 Subject: [PATCH] MT#5349 API: Implement domain preferences. WIP. Missing sanity checks and special handling for certain preferences. --- .../Panel/Controller/API/DomainPreferences.pm | 130 +++++++++++++ .../Controller/API/DomainPreferencesItem.pm | 181 ++++++++++++++++++ lib/NGCP/Panel/Role/API.pm | 49 ++--- lib/NGCP/Panel/Role/API/DomainPreferences.pm | 166 ++++++++++++++++ lib/NGCP/Panel/Role/API/Domains.pm | 39 ++++ lib/NGCP/Panel/Utils/Preferences.pm | 5 +- 6 files changed, 546 insertions(+), 24 deletions(-) create mode 100644 lib/NGCP/Panel/Controller/API/DomainPreferences.pm create mode 100644 lib/NGCP/Panel/Controller/API/DomainPreferencesItem.pm create mode 100644 lib/NGCP/Panel/Role/API/DomainPreferences.pm diff --git a/lib/NGCP/Panel/Controller/API/DomainPreferences.pm b/lib/NGCP/Panel/Controller/API/DomainPreferences.pm new file mode 100644 index 0000000000..4f2e93d585 --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/DomainPreferences.pm @@ -0,0 +1,130 @@ +package NGCP::Panel::Controller::API::DomainPreferences; +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; + +with 'NGCP::Panel::Role::API'; +with 'NGCP::Panel::Role::API::DomainPreferences'; + +class_has('resource_name', is => 'ro', default => 'domainpreferences'); +class_has('dispatch_path', is => 'ro', default => '/api/domainpreferences/'); +class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-domainpreferences'); + +__PACKAGE__->config( + action => { + map { $_ => { + ACLDetachTo => '/api/root/invalid_user', + AllowedRole => 'api_admin', + 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 $domains = $self->item_rs($c); + my $total_count = int($domains->count); + $domains = $domains->search(undef, { + page => $page, + rows => $rows, + }); + my (@embedded, @links); + my $form = $self->get_form($c); + for my $domain ($domains->search({}, {order_by => {-asc => 'me.id'}})->all) { + push @embedded, $self->hal_from_item($c, $domain, $form); + push @links, Data::HAL::Link->new( + relation => 'ngcp:'.$self->resource_name, + href => sprintf('%s%d', $self->dispatch_path, $domain->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 $rname = $self->resource_name; + 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-$rname)"|rel="item $1"|; + s/rel=self/rel="collection 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) = @_; + $c->forward(qw(GET)); + $c->response->body(q()); + return; +} + +sub OPTIONS :Allow { + my ($self, $c) = @_; + my $allowed_methods = $self->allowed_methods; + $c->response->headers(HTTP::Headers->new( + Allow => $allowed_methods->join(', '), + Accept_Post => 'application/hal+json; profile=http://purl.org/sipwise/ngcp-api/#rel-'.$self->resource_name, + )); + $c->response->content_type('application/json'); + $c->response->body(JSON::to_json({ methods => $allowed_methods })."\n"); + return; +} + +sub end : Private { + my ($self, $c) = @_; + + $self->log_response($c); +} + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Controller/API/DomainPreferencesItem.pm b/lib/NGCP/Panel/Controller/API/DomainPreferencesItem.pm new file mode 100644 index 0000000000..98380e8c17 --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/DomainPreferencesItem.pm @@ -0,0 +1,181 @@ +package NGCP::Panel::Controller::API::DomainPreferencesItem; +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::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'; +with 'NGCP::Panel::Role::API::DomainPreferences'; + +class_has('resource_name', is => 'ro', default => 'domainpreferences'); +class_has('dispatch_path', is => 'ro', default => '/api/domainpreferences/'); +class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-domainpreferences'); + +__PACKAGE__->config( + action => { + map { $_ => { + ACLDetachTo => '/api/root/invalid_user', + AllowedRole => 'api_admin', + 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 $domain = $self->item_by_id($c, $id); + last unless $self->resource_exists($c, domainpreference => $domain); + + my $hal = $self->hal_from_item($c, $domain); + + 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', + ops => [qw/add replace remove copy/], + ); + last unless $json; + + my $domain = $self->item_by_id($c, $id); + last unless $self->resource_exists($c, domainpreferences => $domain); + my $old_resource = $self->get_resource($c, $domain); + 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 + $domain = $self->update_item($c, $domain, $old_resource, $resource, 0); + last unless $domain; + + $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, $domain); + 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 $domain = $self->item_by_id($c, $id); + last unless $self->resource_exists($c, systemcontact => $domain); + 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, $domain); + + # last param is "replace" to delete all existing prefs + # for proper PUT behavior + $domain = $self->update_item($c, $domain, $old_resource, $resource, 1); + last unless $domain; + + $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, $domain); + 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 end : Private { + my ($self, $c) = @_; + + $self->log_response($c); +} + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Role/API.pm b/lib/NGCP/Panel/Role/API.pm index 561b70ada8..7d545f609b 100644 --- a/lib/NGCP/Panel/Role/API.pm +++ b/lib/NGCP/Panel/Role/API.pm @@ -2,12 +2,13 @@ package NGCP::Panel::Role::API; use Moose::Role; use Sipwise::Base; +use Storable qw(); use JSON qw(); use JSON::Pointer; use JSON::Types qw(bool); use HTTP::Status qw(:constants); use Safe::Isa qw($_isa); -use Try::Tiny; +use TryCatch; use DateTime::Format::HTTP qw(); use DateTime::Format::RFC3339 qw(); use Types::Standard qw(InstanceOf); @@ -55,6 +56,7 @@ sub get_valid_patch_data { my $c = $params{c}; my $media_type = $params{media_type}; my $id = $params{id}; + my $ops = $params{ops} // [qw/replace copy/]; return unless $self->valid_id($c, $id); return unless $self->forbid_link_header($c); @@ -62,7 +64,7 @@ sub get_valid_patch_data { return unless $self->require_body($c); my $json = $c->stash->{body}; return unless $self->require_wellformed_json($c, $media_type, $json); - return unless $self->require_valid_patch($c, $json); + return unless $self->require_valid_patch($c, $json, $ops); return $json; } @@ -203,9 +205,9 @@ sub require_wellformed_json { try { NGCP::Panel::Utils::ValidateJSON->new($patch); $ret = 1; - } catch { - $self->error($c, HTTP_BAD_REQUEST, "The entity is not a well-formed '$media_type' document. $_"); - }; + } catch($e) { + $self->error($c, HTTP_BAD_REQUEST, "The entity is not a well-formed '$media_type' document. $e"); + } return $ret; } @@ -268,18 +270,21 @@ sub valid_id { } sub require_valid_patch { - my ($self, $c, $json) = @_; + my ($self, $c, $json, $ops) = @_; my $valid_ops = { 'replace' => { 'path' => 1, 'value' => 1 }, 'copy' => { 'from' => 1, 'path' => 1 }, - # we don't support those, as they're quite useless in our case - # TODO: maybe except for remove, which might set it to null? - #'test' => { 'path' => 1, 'value' => 1 }, - #'move' => { 'from' => 1, 'path' => 1 }, - #'remove' => { 'path' => 1 }, - #'add' => { 'path' => 1, 'value' => 1 }, + 'remove' => { 'path' => 1 }, + 'add' => { 'path' => 1, 'value' => 1 }, + 'test' => { 'path' => 1, 'value' => 1 }, + 'move' => { 'from' => 1, 'path' => 1 }, }; + for my $o(keys %{ $valid_ops }) { + unless(grep { /^$o$/ } @{ $ops }) { + delete $valid_ops->{$o} + } + } my $patch = JSON::from_json($json); unless(ref $patch eq "ARRAY") { @@ -299,8 +304,8 @@ sub require_valid_patch { $self->error($c, HTTP_BAD_REQUEST, "Invalid PATCH op '$elem->{op}', must be one of " . (join(', ', map { "'".$_."'" } keys %{ $valid_ops }) )); return; } - my $tmpelem = { %{ $elem } }; - my $tmpops = { %{ $valid_ops } }; + my $tmpelem = Storable::dclone($elem); + my $tmpops = Storable::dclone($valid_ops); my $op = delete $tmpelem->{op}; foreach my $k(keys %{ $tmpelem }) { unless(exists $tmpops->{$op}->{$k}) { @@ -329,10 +334,10 @@ sub resource_exists { sub apply_patch { my ($self, $c, $entity, $json) = @_; my $patch = JSON::decode_json($json); - for my $op (@{ $patch }) { - my $coderef = JSON::Pointer->can($op->{op}); - die "invalid op '".$op->{op}."' despite schema validation" unless $coderef; - try { + try { + for my $op (@{ $patch }) { + my $coderef = JSON::Pointer->can($op->{op}); + die "invalid op '".$op->{op}."' despite schema validation" unless $coderef; for ($op->{op}) { if ('add' eq $_ or 'replace' eq $_) { $entity = $coderef->('JSON::Pointer', $entity, $op->{path}, $op->{value}); @@ -345,10 +350,10 @@ sub apply_patch { unless $coderef->('JSON::Pointer', $entity, $op->{path}, $op->{value}); } } - } catch { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "The entity could not be processed: $_"); - return; - }; + } + } catch($e) { + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "The entity could not be processed: $e"); + return; } return $entity; } diff --git a/lib/NGCP/Panel/Role/API/DomainPreferences.pm b/lib/NGCP/Panel/Role/API/DomainPreferences.pm new file mode 100644 index 0000000000..ae4d6235aa --- /dev/null +++ b/lib/NGCP/Panel/Role/API/DomainPreferences.pm @@ -0,0 +1,166 @@ +package NGCP::Panel::Role::API::DomainPreferences; +use Moose::Role; +use Sipwise::Base; + +use boolean qw(true); +use TryCatch; +use Data::HAL qw(); +use Data::HAL::Link qw(); +use HTTP::Status qw(:constants); +use JSON::Types; +use NGCP::Panel::Utils::XMLDispatcher; +use NGCP::Panel::Utils::Prosody; + +sub get_form { + my ($self, $c) = @_; + if($c->user->roles eq "api_admin") { + return NGCP::Panel::Form::Domain::Admin->new; + } elsif($c->user->roles eq "api_reseller") { + return NGCP::Panel::Form::Domain::Reseller->new; + } + return; +} + +sub hal_from_item { + my ($self, $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("%s", $self->dispatch_path)), + 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:domains', href => sprintf("/api/domains/%d", $item->id)), + ], + relation => 'ngcp:'.$self->resource_name, + ); + + my $resource = $self->get_resource($c, $item); + $hal->resource($resource); + return $hal; +} + +sub get_resource { + my ($self, $c, $item) = @_; + + my $prefs = $item->provisioning_voip_domain->voip_dom_preferences->search({ + 'attribute.internal' => 0, + }, { + join => 'attribute' + }); + + my $resource; + foreach my $pref($prefs->all) { + my $value; + given($pref->attribute->data_type) { + when("int") { $value = int($pref->value) if($pref->value->is_int) } + when("boolean") { $value = JSON::Types::bool($pref->value) if(defined $pref->value) } + default { $value = $pref->value } + } + $resource->{$pref->attribute->attribute} = $value; + } + $resource->{domain_id} = int($item->id); + $resource->{domainpreferences_id} = int($item->id); + return $resource; +} + +sub item_rs { + my ($self, $c) = @_; + + # we actually return the domain rs here, as we can easily + # go to dom_preferences from there + my $item_rs; + if($c->user->roles eq "api_admin") { + $item_rs = $c->model('DB')->resultset('domains'); + } elsif($c->user->roles eq "api_reseller") { + $item_rs = $c->model('DB')->resultset('admins')->find( + { id => $c->user->id, } ) + ->reseller + ->domain_resellers + ->search_related('domain'); + } + return $item_rs; +} + +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, $replace) = @_; + + delete $resource->{id}; + delete $resource->{domain_id}; + delete $resource->{domainpreferences_id}; + + if($replace) { + # in case of PUT, we remove all old entries + try { + $item->provisioning_voip_domain->voip_dom_preferences->delete_all; + } catch($e) { + $c->log->error("failed to clear preferences for domain '".$item->domain."': $e"); + $self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error."); + return; + }; + } else { + # in case of PATCH, we remove only those entries marked for removal in the patch + try { + foreach my $k(keys %{ $old_resource }) { + unless(exists $resource->{$k}) { + my $rs = NGCP::Panel::Utils::Preferences::get_dom_preference_rs( + c => $c, + attribute => $k, + prov_domain => $item->provisioning_voip_domain, + ); + next unless $rs; # unknown resource, just ignore + $rs->first->delete if($rs->first); + } + } + } catch($e) { + $c->log->error("failed to clear preference for domain '".$item->domain."': $e"); + $self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error."); + return; + }; + } + + foreach my $pref(keys %{ $resource }) { + my $rs = NGCP::Panel::Utils::Preferences::get_dom_preference_rs( + c => $c, + attribute => $pref, + prov_domain => $item->provisioning_voip_domain, + ); + unless($rs) { + $c->log->debug("removing unknown dom_preference '$pref' from update"); + next; + } + + # TODO: special handling for different prefs, but otherwise: + + # TODO: syntax checks? + + try { + if($rs->first) { + $rs->first->update({ value => $resource->{$pref} }); + } else { + $rs->create({ value => $resource->{$pref} }); + } + } catch($e) { + $c->log->error("failed to update preference for domain '".$item->domain."': $e"); + $self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error."); + return; + }; + } + + return $item; +} + +1; +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Role/API/Domains.pm b/lib/NGCP/Panel/Role/API/Domains.pm index 73a0baebdf..928fa28639 100644 --- a/lib/NGCP/Panel/Role/API/Domains.pm +++ b/lib/NGCP/Panel/Role/API/Domains.pm @@ -7,6 +7,7 @@ use Try::Tiny; use Data::HAL qw(); use Data::HAL::Link qw(); use HTTP::Status qw(:constants); +use JSON::Types; use NGCP::Panel::Form::Domain::Admin qw(); use NGCP::Panel::Form::Domain::Reseller qw(); use NGCP::Panel::Utils::XMLDispatcher; @@ -37,6 +38,8 @@ sub hal_from_item { Data::HAL::Link->new(relation => 'collection', href => sprintf("/api/%s/", $self->resource_name)), Data::HAL::Link->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'), Data::HAL::Link->new(relation => 'self', href => sprintf("%s%d", $self->dispatch_path, $item->id)), + #( map { $_->attribute->internal ? () : Data::HAL::Link->new(relation => 'ngcp:domainpreferences', href => sprintf("/api/domainpreferences/%d", $_->id), name => $_->attribute->attribute) } $item->provisioning_voip_domain->voip_dom_preferences->all ), + Data::HAL::Link->new(relation => 'ngcp:domainpreferences', href => sprintf("/api/domainpreferences/%d", $item->id)), ], relation => 'ngcp:'.$self->resource_name, ); @@ -51,6 +54,42 @@ sub hal_from_item { ); $resource{id} = int($item->id); + +=pod + # TODO: do we really want to provide this info, as you can't actually + # PUT/PATCH/POST it? Or should you? + $resource{preferences} = {}; + foreach my $pref($item->provisioning_voip_domain->voip_dom_preferences->all) { + next if($pref->attribute->internal); + my $plain = { "boolean" => 1, "int" => 1, "string" => 1 }; + if(exists $plain->{$pref->attribute->data_type}) { + # plain key/value pairs + my $value; + given($pref->attribute->data_type) { + when("int") { $value = int($pref->value) } + when("boolean") { $value = JSON::Types::bool($pref->value) } + default { $value = $pref->value } + } + if($pref->attribute->max_occur <= 1) { + $resource{preferences}{$pref->attribute->attribute} = $value; + } else { + $resource{preferences}{$pref->attribute->attribute} = [] + unless(exists $resource{preferences}{$pref->attribute->attribute}); + push @{ $resource{preferences}{$pref->attribute->attribute} }, $value; + } + } else { + # enum mappings + my $value; + given($pref->attribute->data_type) { + when("int") { $value = int($pref->value) } + when("boolean") { $value = JSON::Types::bool($pref->value) } + default { $value = $pref->value } + } + $resource{preferences}{$pref->attribute->attribute} = $value; + } + } +=cut + $hal->resource({%resource}); return $hal; } diff --git a/lib/NGCP/Panel/Utils/Preferences.pm b/lib/NGCP/Panel/Utils/Preferences.pm index 8cfedda998..3865ca9240 100644 --- a/lib/NGCP/Panel/Utils/Preferences.pm +++ b/lib/NGCP/Panel/Utils/Preferences.pm @@ -454,10 +454,11 @@ sub get_dom_preference_rs { my $preference = $c->model('DB')->resultset('voip_preferences')->find({ attribute => $attribute, 'dom_pref' => 1, - })->voip_dom_preferences->search_rs({ + }); + return unless($preference); + return $preference->voip_dom_preferences->search_rs({ domain_id => $prov_domain->id, }); - return $preference; } sub get_peer_auth_params {