TT#34015 Introduce /api/phonebookentries

* /api/phonebookentries is used for the
      phonebook_reseller
      phonebook_contract
      phonebook_subscriber data storage interaction

Change-Id: If45390fd3080886e03602216cfbacc33296f1558
changes/70/19870/16
Irina Peshinskaya 8 years ago committed by Kirill Solomko
parent 2252eff81a
commit 60e209ca84

@ -0,0 +1,100 @@
package NGCP::Panel::Controller::API::PhonebookEntries;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use boolean qw(true);
use Data::HAL qw();
use Data::HAL::Link qw();
use HTTP::Headers qw();
use HTTP::Status qw(:constants);
use parent qw/NGCP::Panel::Role::Entities NGCP::Panel::Role::API::PhonebookEntries/;
__PACKAGE__->set_config({
POST => {
'ContentType' => ['text/csv', 'application/json'],
},
allowed_roles => [qw/admin reseller subscriberadmin subscriber/],
});
sub allowed_methods{
return [qw/GET POST OPTIONS HEAD/];
}
sub api_description {
return 'Defines Phonebook number entries. You can POST numbers individually one-by-one using json. To bulk-upload numbers, specify the Content-Type as "text/csv" and POST the CSV in the request body to the collection with an optional parameter "purge_existing=true", like "/api/phonebookentries/?purge_existing=true"';
};
sub query_params {
return [
{
param => 'reseller_id',
description => 'Filter for Phonebook entries belonging to a specific reseeller',
query => {
first => sub {
my $q = shift;
{ reseller_id => $q };
},
second => sub {},
},
},
{
param => 'contract_id',
description => 'Filter for Phonebook entries belonging to a specific contract',
query => {
first => sub {
my $q = shift;
{ contract_id => $q };
},
second => sub {},
},
},
{
param => 'subscriber_id',
description => 'Filter for Phonebook entries belonging to a specific subscriber',
query => {
first => sub {
my $q = shift;
{ subscriber_id => $q };
},
second => sub {},
},
},
{
param => 'number',
description => 'Filter for LNP numbers with a specific number (wildcards possible)',
query => {
first => sub {
my $q = shift;
{ 'me.number' => { like => $q } };
},
second => sub {},
},
},
];
}
sub check_create_csv :Private {
my ($self, $c) = @_;
return 'phonebookentries_list.csv';
}
sub create_csv :Private {
my ($self, $c) = @_;
NGCP::Panel::Utils::Phonebook::create_csv(
c => $c,
);
}
sub create_item {
my ($self, $c, $resource, $form, $process_extras) = @_;
my $rs = $self->_item_rs($c,undef,$resource);#maybe copy-paste it here?
return unless $rs;
my $item = $rs->create($resource);
return $item;
}
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,23 @@
package NGCP::Panel::Controller::API::PhonebookEntriesItem;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use parent qw/NGCP::Panel::Role::EntitiesItem NGCP::Panel::Role::API::PhonebookEntries/;
use Data::HAL qw();
use Data::HAL::Link qw();
use HTTP::Headers qw();
use HTTP::Status qw(:constants);
__PACKAGE__->set_config({
allowed_roles => [qw/admin reseller subscriberadmin subscriber/],
});
sub allowed_methods{
return [qw/GET OPTIONS HEAD PATCH PUT DELETE/];
}
1;
# vim: set tabstop=4 expandtab:

@ -6,6 +6,7 @@ use parent 'Catalyst::Controller';
use NGCP::Panel::Utils::Message;
use NGCP::Panel::Utils::DateTime;
use NGCP::Panel::Utils::Contract;
use NGCP::Panel::Utils::Phonebook;
sub auto :Private {
my ($self, $c) = @_;

@ -0,0 +1,46 @@
package NGCP::Panel::Form::Phonebook::CustomerAPI;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
has_field 'id' => (
type => 'PosInteger',
required => 0,
label => 'Phonebook entry id',
element_attr => {
rel => ['tooltip'],
title => ['Phonebook entry id'],
},
);
has_field 'contract_id' => (
type => 'PosInteger',
required => 0,
label => 'Phonebook entry contract id',
element_attr => {
rel => ['tooltip'],
title => ['Phonebook entry contract id'],
},
);
has_field 'name' => (
type => 'Text',
required => 1,
label => 'Phonebook entry name',
element_attr => {
rel => ['tooltip'],
title => ['The full entry name "e.g. John Smith".'],
},
);
has_field 'number' => (
type => 'Text',
required => 1,
label => 'Phonebook number',
element_attr => {
rel => ['tooltip'],
title => ['The phonebook number, can be either as a numeric or a SIP number.' ],
},
);
1;

@ -0,0 +1,46 @@
package NGCP::Panel::Form::Phonebook::ResellerAPI;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
has_field 'id' => (
type => 'PosInteger',
required => 0,
label => 'Phonebook entry id',
element_attr => {
rel => ['tooltip'],
title => ['Phonebook entry id'],
},
);
has_field 'reseller_id' => (
type => 'PosInteger',
required => 0,
label => 'Phonebook entry reseller id',
element_attr => {
rel => ['tooltip'],
title => ['Phonebook entry subscriber id'],
},
);
has_field 'name' => (
type => 'Text',
required => 1,
label => 'Phonebook entry name',
element_attr => {
rel => ['tooltip'],
title => ['The full entry name "e.g. John Smith".'],
},
);
has_field 'number' => (
type => 'Text',
required => 1,
label => 'Phonebook number',
element_attr => {
rel => ['tooltip'],
title => ['The phonebook number, can be either as a numeric or a SIP number.' ],
},
);
1;

@ -0,0 +1,57 @@
package NGCP::Panel::Form::Phonebook::SubscriberAPI;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
has_field 'id' => (
type => 'PosInteger',
required => 0,
label => 'Phonebook entry id',
element_attr => {
rel => ['tooltip'],
title => ['Phonebook entry id'],
},
);
has_field 'subscriber_id' => (
type => 'PosInteger',
required => 0,
label => 'Phonebook entry subscriber id',
element_attr => {
rel => ['tooltip'],
title => ['Phonebook entry subscriber id'],
},
);
has_field 'name' => (
type => 'Text',
required => 1,
label => 'Phonebook entry name',
element_attr => {
rel => ['tooltip'],
title => ['The full entry name "e.g. John Smith".'],
},
);
has_field 'number' => (
type => 'Text',
required => 1,
label => 'Phonebook number',
element_attr => {
rel => ['tooltip'],
title => ['The phonebook number, can be either as a numeric or a SIP number.' ],
},
);
has_field 'shared' => (
type => 'Boolean',
required => 0,
default_value => 0,
label => 'Share phonebook entry',
element_attr => {
rel => ['tooltip'],
title => ['Define if the Phonebook entry is visible to other subscribers within the same contract'],
},
);
1;

@ -30,54 +30,55 @@ use NGCP::Panel::Utils::Journal qw();
sub get_valid_data{
my ($self, %params) = @_;
my ($data,$resource);
my ($data,$resource,$special_data_process);
my $c = $params{c};
my $method = $params{method};
my $method = $params{method} // uc($c->request->method);
my $media_type = $params{media_type};
my $json_media_type = $params{json_media_type};#for rare specific cases, like text/csv
return unless $self->forbid_link_header($c);
if(('POST' eq $method) || ('PUT' eq $method) ){
if ($method =~ /^(GET|PUT|POST)$/) {
$json_media_type //= 'application/json';
}elsif('PATCH' eq $method){
} elsif ($method eq 'PATCH') {
$json_media_type //= 'application/json-patch+json';
}
return unless $self->valid_media_type($c, $media_type);
if(('PUT' eq $method) || ('PATCH' eq $method)){
if ($method =~ /^(PUT|PATCH)$/) {
my $id = $params{id};
return unless $self->valid_id($c, $id);
}
my ($json_raw,$json);
if('multipart/form-data' eq $c->req->headers->content_type){
if ($c->req->headers->content_type eq 'multipart/form-data') {
return unless $self->require_uploads($c);
$json_raw = $c->req->param('json');
}else{
} else {
return unless $self->require_body($c);
$data = $c->stash->{body};
$resource = $c->req->query_params;
$special_data_process = 1;
}
#if($json_media_type =~/json/i){
if($json_media_type eq 'application/json'
|| $json_media_type eq 'application/json-patch+json' ){
if ($json_media_type eq 'application/json' ||
$json_media_type eq 'application/json-patch+json' ) {
$json_raw //= $data;
return unless $self->require_wellformed_json($c, $json_media_type, $json_raw);
$json = JSON::from_json($json_raw, { utf8 => 1 });
if('PATCH' eq $method){
if ($method eq 'PATCH') {
my $ops = $params{ops} // [qw/replace copy/];
return unless $self->require_valid_patch($c, $json, $ops);
}
return unless $self->get_uploads($c, $json, $params{uploads}, $params{form});
$resource = $json;
$special_data_process = 0;
}
return ($resource, $data);
return ($resource, $data, $special_data_process);
}
sub get_valid_post_data {
@ -560,6 +561,7 @@ sub require_valid_patch {
return 1;
}
sub item_by_id_valid {
my ($self, $c, $id) = @_;
return unless $self->valid_id($c, $id);
@ -567,6 +569,7 @@ sub item_by_id_valid {
return unless $self->resource_exists($c, $self->item_name => $item);
return $item;
}
sub resource_exists {
my ($self, $c, $entity_name, $resource) = @_;
return 1 if $resource;
@ -1218,14 +1221,18 @@ sub return_representation_post{
$preference //= $self->require_preference($c);
return unless $preference;
$hal //= $self->hal_from_item($c, $item, $form, \%params);#form_excptions will goes with params
$response //= HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new(
$hal->http_headers,
), $hal->as_json);
$c->response->status(HTTP_CREATED);
$c->response->header(Location => sprintf('/%s%d', $c->request->path, $self->get_item_id($c, $item)));
if ('minimal' eq $preference) {
if ($item) {
$hal //= $self->hal_from_item($c, $item, $form, \%params);#form_excptions will goes with params
$response //= HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new(
$hal->http_headers,
), $hal->as_json);
$c->response->header(Location => sprintf('/%s%d', $c->request->path, $self->get_item_id($c, $item)));
}
if ('minimal' eq $preference || !$response) {
$c->response->body(q());
}else{
$c->response->body($response->content);

@ -0,0 +1,219 @@
package NGCP::Panel::Role::API::PhonebookEntries;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use parent 'NGCP::Panel::Role::API';
no strict 'refs';
use boolean qw(true);
use Data::HAL qw();
use Data::HAL::Link qw();
use HTTP::Status qw(:constants);
sub resource_name{
return 'phonebookentries';
}
sub dispatch_path{
return '/api/phonebookentries/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-phonebookentries';
}
sub _item_rs {
my ($self, $c, $id, $resource) = @_;
my $params = $c->request->query_params;
my($owner,$type,$parameter,$value) = $self->check_owner_params($c, $params);
return unless $owner;
my $method = 'get_'.$type.'_phonebook_rs';
my ($list_rs,$item_rs) = &$method($c, $value, $type);
return $list_rs;
}
sub item_by_id {
my ($self, $c, $id) = @_;
my $item_rs = $self->item_rs($c, $id);
return unless $item_rs;
return $item_rs->find($id);
}
sub get_form {
my ($self, $c) = @_;
my $params = $c->request->query_params;
if ($params) {
if ($params->{reseller_id}) {
return NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::ResellerAPI", $c);
} elsif ($params->{customer_id}) {
return NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::CustomerAPI", $c);
} elsif ($params->{subscriber_id}) {
return NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::SubscriberAPI", $c);
}
}
if ($c->user->roles eq "admin") {
return NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::ResellerAPI", $c);
} elsif ($c->user->roles eq "reseller") {
return NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::ResellerAPI", $c);
} elsif ($c->user->roles eq 'subscriber' ||
$c->user->roles eq 'subscriberadmin') {
return NGCP::Panel::Form::get("NGCP::Panel::Form::Phonebook::SubscriberAPI", $c);
}
return;
}
sub check_owner_params {
my($self, $c, $params) = @_;
my @allowed_params;
if ($c->user->roles eq "admin") {
@allowed_params = qw/reseller_id customer_id subscriber_id/;
} elsif ($c->user->roles eq "reseller") {
@allowed_params = qw/reseller_id customer_id subscriber_id/;
} elsif ($c->user->roles eq 'subscriberadmin') {
@allowed_params = qw/customer_id subscriber_id/;
} elsif ($c->user->roles eq 'subscriber') {
@allowed_params = qw/subscriber_id/;
}
$params //= $c->request->params;
my %owner_params =
map { $_ => $params->{$_} }
grep { exists $params->{$_} }
(qw/reseller_id customer_id subscriber_id/);
if (!grep { exists $owner_params{$_} } @allowed_params) {
$c->log->error('"'.join('" or "', @allowed_params).'" should be specified');
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, '"'.join('" or "', @allowed_params).'" should be specified.');
return;
}
if (scalar keys %owner_params > 1) {
$c->log->error('Too many owners: '.join(',',keys %owner_params));
$self->error($c, HTTP_UNPROCESSABLE_ENTITY,
sprintf("Only one of either %s should be specified",
join(' or ', @allowed_params)));
return;
}
my $schema = $c->model('DB');
my ($parameter,$value) = each %owner_params;
my ($owner,$type);
unless (is_int($value)) {
$c->log->error('Invalid owner id '.join(',',keys %owner_params));
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid owner id");
return;
}
if ($parameter eq 'reseller_id') {
$type = 'reseller';
if ($c->user->roles eq "admin" ||
($c->user->roles eq "reseller" &&
$c->user->reseller_id == $value)) {
$owner = $schema->resultset('resellers')->find($value);
}
} elsif ($parameter eq 'customer_id') {
$type = 'contract';
if ($c->user->roles eq "admin") {
$owner = $schema->resultset('contracts')->find($value);
} elsif ($c->user->roles eq "reseller") {
$owner = $schema->resultset('contracts')->find({
id => $value,
'contact.reseller_id' => $c->user->reseller_id,
},{
join => 'contact',
});
} elsif ($c->user->roles eq 'subscriberadmin' &&
$c->user->voip_subscriber->contract_id == $value) {
$owner = $schema->resultset('contracts')->find({ id => $value });
}
} elsif ($parameter eq 'subscriber_id') {
$type = 'subscriber';
if ($c->user->roles eq "admin") {
$owner = $schema->resultset('voip_subscribers')->find($value);
} elsif ($c->user->roles eq "reseller") {
$owner = $schema->resultset('voip_subscribers')->find({
id => $value,
'contact.reseller_id' => $c->user->reseller_id,
},{
join => { 'contract' => 'contact' },
});
} elsif (($c->user->roles eq 'subscriberadmin' ||
$c->user->roles eq "subscriber") &&
$c->user->voip_subscriber->id == $value) {
$owner = $schema->resultset('voip_subscribers')->find({ id => $value });
}
}
unless($owner) {
$c->log->error("Unknown $parameter value '$value'");
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Unknown $parameter value '$value'");
return;
}
return ($owner,$type,$parameter,$value);
}
sub get_reseller_phonebook_rs {
my ($c, $reseller_id, $context) = @_;
my $list_rs = $c->model('DB')->resultset('reseller_phonebook')->search_rs({
reseller_id => $reseller_id,
},{
columns => [qw/id name number/,
{ 'owner_id' => 'me.reseller_id' } ,
{ 'shared' => \'0'},
],
});
my $item_rs = $c->model('DB')->resultset('reseller_phonebook');
return ($list_rs,$item_rs);
}
sub get_contract_phonebook_rs {
my ($c, $contract_id, $context) = @_;
my $contract_rs = $c->model('DB')->resultset('contracts')->search({
id => $contract_id,
})->first;
my $contract_pb_rs = $c->model('DB')->resultset('contract_phonebook')->search_rs({
contract_id => $contract_id,
},{
columns => [qw/id name number/,
{ 'owner_id' => 'me.contract_id' } ,
{ 'shared' => \'0'},
],
});
my $list_rs = $contract_pb_rs;
my $item_rs = $c->model('DB')->resultset('contract_phonebook');
return ($list_rs,$item_rs);
}
sub get_subscriber_phonebook_rs {
my ($c, $subscriber_id) = @_;
my $subscriber_rs = $c->model('DB')->resultset('voip_subscribers')->search({
id => $subscriber_id,
})->first;
my $subscriber_pb_rs = $c->model('DB')->resultset('subscriber_phonebook')->search_rs({
subscriber_id => $subscriber_id,
},{
columns => [qw/id name number/,
{ 'owner_id' => 'me.subscriber_id' } ,
{ 'shared' => 'me.shared'},
],
'join' => 'subscriber',
});
my $list_rs = $subscriber_pb_rs;
my $item_rs = $c->model('DB')->resultset('subscriber_phonebook');
return ($list_rs,$item_rs);
}
1;
# vim: set tabstop=4 expandtab:

@ -9,6 +9,7 @@ use HTTP::Headers qw();
use HTTP::Status qw(:constants);
use Data::HAL qw();
use Data::HAL::Link qw();
use TryCatch;
##### --------- common part
@ -248,39 +249,61 @@ sub post {
my ($c) = @_;
my $guard = $self->get_transaction_control($c);
{
my ($form) = $self->get_form($c, 'add');
#instead of type parameter get_form can check request method
my ($form) = $self->get_form($c, 'add');
my $method_config = $self->config->{action}->{POST};
my $process_extras= {};
my ($resource) = $self->get_valid_data(
my ($resource, $data, $special_data_process) = $self->get_valid_data(
c => $c,
method => 'POST',
media_type => $method_config->{ContentType} // 'application/json',
method => 'POST',
media_type => $method_config->{ContentType} // 'application/json',
uploads => $method_config->{Uploads} // [] ,
form => $form,
);
last unless $resource;
#instead of type parameter get_form can check request method
last unless $self->pre_process_form_resource($c, undef, undef, $resource, $form, $process_extras);
last unless $self->validate_form(
c => $c,
resource => $resource,
form => $form,
);
last unless $self->process_form_resource($c, undef, undef, $resource, $form, $process_extras);
last unless $resource;
last unless $self->check_duplicate($c, undef, undef, $resource, $form, $process_extras);
last unless $self->check_resource($c, undef, undef, $resource, $form, $process_extras);
my $item = $self->create_item($c, $resource, $form, $process_extras);
last unless $item;
my ($item,$data_processed_result);
if (!$special_data_process || !$data) {
delete $resource->{purge_existing};
last unless $self->pre_process_form_resource($c, undef, undef, $resource, $form, $process_extras);
last unless $self->validate_form(
c => $c,
resource => $resource,
form => $form,
);
last unless $self->process_form_resource($c, undef, undef, $resource, $form, $process_extras);
last unless $resource;
last unless $self->check_duplicate($c, undef, undef, $resource, $form, $process_extras);
last unless $self->check_resource($c, undef, undef, $resource, $form, $process_extras);
$item = $self->create_item($c, $resource, $form, $process_extras);
last unless $item;
} else {
try {
#$processed_ok(array), $processed_failed(array), $info, $error
$data_processed_result = $self->upload_data(
c => $c,
data => \$data,
resource => $resource,
form => $form,
process_extras => $process_extras,
);
} catch($e) {
$c->log->error("failed to upload csv: $e");
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error");
last;
};
}
$self->complete_transaction($c);
$self->post_process_commit($c, 'create', $item, undef, $resource, $form, $process_extras);
return if defined $c->stash->{api_error_message};
$self->return_representation_post($c, 'item' => $item, 'form' => $form );
$self->return_representation_post($c,
'item' => $item,
'form' => $form,
data_processed_result => $data_processed_result );
}
return;
}

Loading…
Cancel
Save