From 34bc2d54855eea077a73339649da26ecfc7442f5 Mon Sep 17 00:00:00 2001 From: Irina Peshinskaya Date: Wed, 27 Sep 2017 03:27:55 +0300 Subject: [PATCH] TT#21348 Add Item class for the conversations API Change-Id: Id357e747842d5c3e0f11634dfd5fa950ca36cb1c --- lib/NGCP/Panel/Controller/API/CallLists.pm | 3 +- .../Panel/Controller/API/CallListsItem.pm | 3 +- .../Panel/Controller/API/ConversationsItem.pm | 16 + lib/NGCP/Panel/Form/Conversation/API.pm | 94 ++++- lib/NGCP/Panel/Role/API.pm | 33 +- lib/NGCP/Panel/Role/API/CallLists.pm | 71 +--- lib/NGCP/Panel/Role/API/Conversations.pm | 392 +++++++++++------- lib/NGCP/Panel/Role/Entities.pm | 9 +- lib/NGCP/Panel/Role/EntitiesItem.pm | 26 +- lib/NGCP/Panel/Utils/API/Calllist.pm | 111 +++++ lib/NGCP/Panel/Utils/CallList.pm | 4 +- lib/NGCP/Panel/Utils/Generic.pm | 37 +- 12 files changed, 546 insertions(+), 253 deletions(-) create mode 100644 lib/NGCP/Panel/Controller/API/ConversationsItem.pm create mode 100644 lib/NGCP/Panel/Utils/API/Calllist.pm diff --git a/lib/NGCP/Panel/Controller/API/CallLists.pm b/lib/NGCP/Panel/Controller/API/CallLists.pm index ff73ccf9da..e257e8562f 100644 --- a/lib/NGCP/Panel/Controller/API/CallLists.pm +++ b/lib/NGCP/Panel/Controller/API/CallLists.pm @@ -10,6 +10,7 @@ use HTTP::Headers qw(); use HTTP::Status qw(:constants); use NGCP::Panel::Utils::DateTime; +use NGCP::Panel::Utils::API::Calllist; use DateTime::TimeZone; use NGCP::Panel::Utils::CallList qw(); require Catalyst::ActionRole::ACL; @@ -312,7 +313,7 @@ sub GET :Allow { return; } - my $owner = $self->get_owner_data($c, $schema); + my $owner = NGCP::Panel::Utils::API::Calllist::get_owner_data($self, $c, $schema); last unless $owner; $c->stash(owner => $owner); # for query_param: direction my $items = $self->item_rs($c); diff --git a/lib/NGCP/Panel/Controller/API/CallListsItem.pm b/lib/NGCP/Panel/Controller/API/CallListsItem.pm index 93a55208f4..7ef5ad7ff3 100644 --- a/lib/NGCP/Panel/Controller/API/CallListsItem.pm +++ b/lib/NGCP/Panel/Controller/API/CallListsItem.pm @@ -5,6 +5,7 @@ use Sipwise::Base; use HTTP::Headers qw(); use HTTP::Status qw(:constants); +use NGCP::Panel::Utils::API::Calllist; use NGCP::Panel::Utils::ValidateJSON qw(); use DateTime::TimeZone; @@ -65,7 +66,7 @@ sub GET :Allow { my $schema = $c->model('DB'); last unless $self->valid_id($c, $id); - my $owner = $self->get_owner_data($c, $schema); + my $owner = NGCP::Panel::Utils::API::Calllist::get_owner_data($self, $c, $schema); last unless $owner; my $href_data = $owner->{subscriber} ? "subscriber_id=".$owner->{subscriber}->id : diff --git a/lib/NGCP/Panel/Controller/API/ConversationsItem.pm b/lib/NGCP/Panel/Controller/API/ConversationsItem.pm new file mode 100644 index 0000000000..64648008da --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/ConversationsItem.pm @@ -0,0 +1,16 @@ +package NGCP::Panel::Controller::API::ConversationsItem; + +use Sipwise::Base; +use NGCP::Panel::Utils::Generic qw(:all); + +use parent qw/NGCP::Panel::Role::EntitiesItem NGCP::Panel::Role::API::Conversations/; + +__PACKAGE__->set_config(); + +sub allowed_methods{ + return [qw/GET OPTIONS HEAD/]; +} + +1; + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/Conversation/API.pm b/lib/NGCP/Panel/Form/Conversation/API.pm index 3fcabf3590..f7d01b4975 100644 --- a/lib/NGCP/Panel/Form/Conversation/API.pm +++ b/lib/NGCP/Panel/Form/Conversation/API.pm @@ -3,22 +3,106 @@ package NGCP::Panel::Form::Conversation::API; use HTML::FormHandler::Moose; extends 'HTML::FormHandler'; +has_field 'id' => ( + type => 'PosInteger', + label => 'The original conversation record id - cdr id/voicemail id/sms id/fax journal record id/prosody message archive mgmt (mam) record id.', + required => 1, +); + +has_field 'call_id' => ( + type => 'Text', + label => 'Call id.', + required => 1, +); + +has_field 'call_type' => ( + type => 'Text', + label => 'One of the "call","cfu","cft","cfb","cfna".', + required => 1, +); + +has_field 'start_time' => ( + type => 'Text', + label => 'The timestamp of the conversation event.', + required => 1, +); + has_field 'type' => ( type => 'Text', label => 'The conversation event type: call/voicemail/sms/fax/xmpp.', required => 1, ); -has_field 'id' => ( - type => 'PosInteger', - label => 'The original conversation record id - cdr id/voicemail id/sms id/fax journal record id/prosody message archive mgmt (mam) record id.', +has_field 'status' => ( + type => 'Text', + label => 'Status of the conversation. Possible values are: "ok","busy","noanswer","cancel","offline","timeout","other".', required => 1, ); -has_field 'timestamp' => ( +has_field 'rating_status' => ( type => 'Text', - label => 'The timestamp of the conversation event.', + label => 'Status of the rate processing for the conversation. Possible values are: "unrated","ok","failed".', + required => 1, +); + +has_field 'caller' => ( + type => 'Text', + label => 'Conversation initiator.', required => 1, ); +has_field 'callee' => ( + type => 'Text', + label => 'Conversation receiver.', + required => 1, +); + +has_field 'direction' => ( + type => 'Text', + label => 'Conversation direction.', + required => 1, +); + +has_field 'duration' => ( + type => 'Text', + label => 'Conversation duration.', + required => 1, +); + +has_field 'subscriber_id' => ( + type => 'PosInteger', + label => 'Subscriber who can manage fax record.', + required => 0, +); + +has_field 'pages' => ( + type => 'Integer', + label => 'Number of the pages in the fax document.', + required => 0, +); + +has_field 'filename' => ( + type => 'Text', + label => 'Filename of the fax document.', + required => 0, +); + +has_field 'folder' => ( + type => 'Text', + label => 'The folder the message is currently in (one of INBOX, Old, Work, Friends, Family, Cust1-Cust6).', + required => 0, +); + +has_field 'context' => ( + type => 'Text', + label => 'TBD', + required => 0, +); + +has_field 'voicemail_subscriber_id' => ( + type => 'Integer', + label => 'The subscriber id the message belongs to.', + required => 0, +); + 1; diff --git a/lib/NGCP/Panel/Role/API.pm b/lib/NGCP/Panel/Role/API.pm index aff0d925e3..17556e4308 100644 --- a/lib/NGCP/Panel/Role/API.pm +++ b/lib/NGCP/Panel/Role/API.pm @@ -589,6 +589,7 @@ sub paginate_order_collection_rs { my ($self, $c, $item_rs, $params) = @_; my($page,$rows,$order_by,$direction) = @$params{qw/page rows order_by direction/}; + my $result_class = $item_rs->result_class(); my $total_count = int($item_rs->count); $item_rs = $item_rs->search(undef, { page => $page, @@ -608,6 +609,11 @@ sub paginate_order_collection_rs { $c->log->debug("ordering by $col"); } } + my $result_class_after = $item_rs->result_class(); + if($result_class ne $result_class_after){ + $item_rs->result_class($result_class); + } + return ($total_count, $item_rs); } @@ -891,6 +897,7 @@ sub hal_from_item { } my $resource = $self->resource_from_item($c, $item, $form); $resource = $self->process_hal_resource($c, $item, $resource, $form); + return unless $resource; my $links = $self->hal_links($c, $item, $resource, $form) // []; my $hal = NGCP::Panel::Utils::DataHal->new( links => [ @@ -902,8 +909,22 @@ sub hal_from_item { ), NGCP::Panel::Utils::DataHalLink->new(relation => 'collection', href => sprintf("/api/%s/", $self->resource_name)), NGCP::Panel::Utils::DataHalLink->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'), - NGCP::Panel::Utils::DataHalLink->new(relation => 'self', href => sprintf("%s%s", $self->dispatch_path, $self->get_item_id($c, $item))), - NGCP::Panel::Utils::DataHalLink->new(relation => "ngcp:".$self->resource_name, href => sprintf("/api/%s/%s", $self->resource_name, $self->get_item_id($c, $item))), + NGCP::Panel::Utils::DataHalLink->new( + relation => 'self', + href => sprintf( + "%s%s", + $self->dispatch_path, + $self->get_item_id($c, $item, undef, undef, { purpose => 'hal_links_href' }) + ), + ), + NGCP::Panel::Utils::DataHalLink->new( + relation => "ngcp:".$self->resource_name, + href => sprintf( + "/api/%s/%s", + $self->resource_name, + $self->get_item_id($c, $item, undef, undef, { purpose => 'hal_links_href' }) + ) + ), @$links ], relation => 'ngcp:'.$self->resource_name, @@ -1027,8 +1048,13 @@ sub get_form { return ; } +sub get_list{ + my ($self, $c) = @_; + return $self->item_rs($c); +} + sub get_item_id{ - my($self, $c, $item, $resource, $form) = @_; + my($self, $c, $item, $resource, $form, $params) = @_; return int(blessed $item ? $item->id : $item->{id}); } @@ -1189,5 +1215,6 @@ sub return_requested_type { $self->error($c, HTTP_BAD_REQUEST, $e); } } + 1; # vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Role/API/CallLists.pm b/lib/NGCP/Panel/Role/API/CallLists.pm index fd24c3c0b3..0ba7cd1f8a 100644 --- a/lib/NGCP/Panel/Role/API/CallLists.pm +++ b/lib/NGCP/Panel/Role/API/CallLists.pm @@ -126,76 +126,7 @@ sub item_by_id { return $item_rs->find($id); } -sub get_owner_data { - my ($self, $c, $schema) = @_; - - my $ret; - if($c->user->roles eq "admin" || $c->user->roles eq "reseller") { - if($c->req->param('subscriber_id')) { - my $sub = $schema->resultset('voip_subscribers')->find($c->req->param('subscriber_id')); - unless($sub) { - $self->error($c, HTTP_NOT_FOUND, "Invalid 'subscriber_id'."); - return; - } - if($c->user->roles eq "reseller" && $sub->contract->contact->reseller_id != $c->user->reseller_id) { - $self->error($c, HTTP_NOT_FOUND, "Invalid 'subscriber_id'."); - return; - } - return { - subscriber => $sub, - customer => $sub->contract, - }; - } elsif($c->req->param('customer_id')) { - my $cust = $schema->resultset('contracts')->find($c->req->param('customer_id')); - unless($cust && $cust->contact->reseller_id) { - $self->error($c, HTTP_NOT_FOUND, "Invalid 'customer_id'."); - return; - } - if($c->user->roles eq "reseller" && $cust->contact->reseller_id != $c->user->reseller_id) { - $self->error($c, HTTP_NOT_FOUND, "Invalid 'customer_id'."); - return; - } - return { - subscriber => undef, - customer => $cust, - }; - } else { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Mandatory parameter 'subscriber_id' or 'customer_id' missing in request"); - return; - } - } elsif($c->user->roles eq "subscriberadmin") { - if($c->req->param('subscriber_id')) { - my $sub = $schema->resultset('voip_subscribers')->find($c->req->param('subscriber_id')); - unless($sub) { - $self->error($c, HTTP_NOT_FOUND, "Invalid 'subscriber_id'."); - return; - } - if($sub->contract_id != $c->user->account_id) { - $self->error($c, HTTP_NOT_FOUND, "Invalid 'subscriber_id'."); - return; - } - return { - subscriber => $sub, - customer => $sub->contract, - }; - } else { - my $cust = $schema->resultset('contracts')->find($c->user->account_id); - unless($cust && $cust->contact->reseller_id) { - $self->error($c, HTTP_NOT_FOUND, "Invalid 'customer_id'."); - return; - } - return { - subscriber => undef, - customer => $cust, - }; - } - } else { - return { - subscriber => $c->user->voip_subscriber, - customer => $c->user->voip_subscriber->contract, - }; - } -} + 1; # vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Role/API/Conversations.pm b/lib/NGCP/Panel/Role/API/Conversations.pm index 3761f38c13..078924c076 100644 --- a/lib/NGCP/Panel/Role/API/Conversations.pm +++ b/lib/NGCP/Panel/Role/API/Conversations.pm @@ -3,15 +3,19 @@ package NGCP::Panel::Role::API::Conversations; use parent qw/NGCP::Panel::Role::API/; use Sipwise::Base; -#use NGCP::Panel::Utils::Generic qw(:all); +use NGCP::Panel::Utils::Generic qw(:all); use NGCP::Panel::Utils::CallList qw(); +use NGCP::Panel::Utils::API::Calllist qw(); +use NGCP::Panel::Utils::Fax; use NGCP::Panel::Utils::DateTime qw(); use DateTime::Format::Strptime qw(); use HTTP::Status qw(:constants); use NGCP::Panel::Form; +use Data::Dumper; use Tie::IxHash; +#use Class::Hash; my %call_fields = (); my $call_fields_tied = tie(%call_fields, 'Tie::IxHash'); @@ -38,53 +42,33 @@ $call_fields{source_customer_cost} = 'me.source_customer_cost'; $call_fields{destination_customer_cost} = 'me.destination_customer_cost'; $call_fields{source_customer_free_time} = 'me.source_customer_free_time'; -my $cdr_proto = _hash2obj( - classname => 'cdr_item', - accessors => { - (map { $_ => _get_alias($call_fields_tied->Indices($_) + 1); } keys %call_fields), - #call_id => sub { #override - # my $self = shift; - # use Data::Dumper; - # my $test = { %$self }; - # delete $test->{c}; - # $self->{c}->log->debug(Dumper([map { $_ => _get_alias($call_fields_tied->Indices($_) + 1); } keys %call_fields])); - # return $self->{'field15'}; - #}, - get_column => sub { - my ($self,$colname) = @_; - return $self->{$colname}; - }, - source_subscriber => sub { - my $self = shift; - return $self->{c}->model('DB')->resultset('voip_subscribers')->find({ uuid => $self->source_user_id() }); - }, - destination_subscriber => sub { - my $self = shift; - return $self->{c}->model('DB')->resultset('voip_subscribers')->find({ uuid => $self->destination_user_id() }); - }, - }, -); - +#this is exactly cdr item, although we take call fields. +#Call fields contain everyithing that process_cdr_item requires my %voicemail_fields = (); -tie(%voicemail_fields, 'Tie::IxHash'); +my $voicemail_fields_tied = tie(%voicemail_fields, 'Tie::IxHash'); $voicemail_fields{duration} = 'me.duration'; $voicemail_fields{origtime} = 'me.origtime'; $voicemail_fields{callerid} = 'me.callerid'; +$voicemail_fields{callee} = 'me.callerid'; $voicemail_fields{mailboxuser} = 'me.mailboxuser'; +$voicemail_fields{context} = 'me.context'; +$voicemail_fields{macrocontext} = 'me.macrocontext'; +$voicemail_fields{mailboxcontext} = 'me.mailboxcontext'; $voicemail_fields{dir} = 'me.dir'; my %sms_fields = (); -tie(%sms_fields, 'Tie::IxHash'); +my $sms_fields_tied = tie(%sms_fields, 'Tie::IxHash'); $sms_fields{subscriber_id} = 'me.subscriber_id'; $sms_fields{time} = 'me.time'; $sms_fields{direction} = 'me.direction'; -$sms_fields{caller} = 'me.callee'; -$sms_fields{text} = 'me.text'; +$sms_fields{caller} = 'me.caller'; +$sms_fields{callee} = 'me.callee'; +$sms_fields{text} = 'me.text'; $sms_fields{reason} = 'me.reason'; $sms_fields{status} = 'me.status'; my %fax_fields = (); -tie(%fax_fields, 'Tie::IxHash'); +my $fax_fields_tied = tie(%fax_fields, 'Tie::IxHash'); $fax_fields{subscriber_id} = 'me.subscriber_id'; $fax_fields{time} = 'me.time'; $fax_fields{direction} = 'me.direction'; @@ -102,7 +86,7 @@ $fax_fields{caller_uuid} = 'me.caller_uuid'; $fax_fields{callee_uuid} = 'me.callee_uuid'; my %xmpp_fields = (); -tie(%xmpp_fields, 'Tie::IxHash'); +my $xmpp_fields_tied = tie(%xmpp_fields, 'Tie::IxHash'); $xmpp_fields{subscriber_id} = 'me.id'; $xmpp_fields{user} = 'me.user'; $xmpp_fields{with} = 'me.with'; @@ -115,12 +99,85 @@ $max_fields = scalar keys %sms_fields if ((scalar keys %sms_fields) > $max_field $max_fields = scalar keys %fax_fields if ((scalar keys %fax_fields) > $max_fields); $max_fields = scalar keys %xmpp_fields if ((scalar keys %xmpp_fields) > $max_fields); + +my $cdr_proto = NGCP::Panel::Utils::Generic::hash2obj( + classname => 'cdr_item', + accessors => { + %{_get_fields_names(\%call_fields,$call_fields_tied)}, + get_column => sub { + my ($self,$colname) = @_; + return $self->{$colname}; + }, + source_subscriber => sub { + my $self = shift; + return $self->{c}->model('DB')->resultset('voip_subscribers')->find({ uuid => $self->source_user_id() }); + }, + destination_subscriber => sub { + my $self = shift; + return $self->{c}->model('DB')->resultset('voip_subscribers')->find({ uuid => $self->destination_user_id() }); + }, + }, +); +my $fax_proto = NGCP::Panel::Utils::Generic::hash2obj( + classname => 'fax_item', + accessors => { + %{_get_fields_names(\%fax_fields,$fax_fields_tied)}, + get_column => sub { + my ($self,$colname) = @_; + return $self->{$colname}; + }, + caller_subscriber => sub { + my $self = shift; + return $self->{c}->model('DB')->resultset('voip_subscribers')->find({ uuid => $self->caller_uuid() }); + }, + callee_subscriber => sub { + my $self = shift; + return $self->{c}->model('DB')->resultset('voip_subscribers')->find({ uuid => $self->callee_uuid() }); + }, + provisioning_voip_subscriber => sub { + my $self = shift; + return $self->{c}->model('DB')->resultset('provisioning_voip_subscribers')->find({ id => $self->suscriber_id() }); + }, + }, +); +my $voicemail_proto = NGCP::Panel::Utils::Generic::hash2obj( + classname => 'voicemail_item', + accessors => { + %{_get_fields_names(\%voicemail_fields,$voicemail_fields_tied)}, + get_column => sub { + my ($self,$colname) = @_; + return $self->{$colname}; + }, + }, +); +my $sms_proto = NGCP::Panel::Utils::Generic::hash2obj( + classname => 'sms_item', + accessors => { + %{_get_fields_names(\%sms_fields,$sms_fields_tied)}, + get_column => sub { + my ($self,$colname) = @_; + return $self->{$colname}; + }, + }, +); + +my $xmpp_proto = NGCP::Panel::Utils::Generic::hash2obj( + classname => 'xmpp_item', + accessors => { + %{_get_fields_names(\%xmpp_fields,$xmpp_fields_tied)}, + get_column => sub { + my ($self,$colname) = @_; + return $self->{$colname}; + }, + }, +); + my %enabled_conversations = ( - call => 1, + call => 1, voicemail => 1, - sms => 1, - fax => 1, - xmpp => 0, + sms => 1, + fax => 1, + xmpp => 0, ); sub item_name{ @@ -143,29 +200,59 @@ sub config_allowed_roles { return [qw/admin reseller subscriberadmin subscriber/]; } +sub get_list{ + my ($self, $c) = @_; +#TODO: move to config and return to the SUPER (Entities) again +#So: if config->{methtod}->{required_params} eq '' owner +#and for other types of possible required parameters or predefined parameters groups + my $schema = $c->model('DB'); + my $owner = NGCP::Panel::Utils::API::Calllist::get_owner_data($self, $c, $schema); + unless (defined $owner) { + return; + } + return $self->item_rs($c, $owner); +} + +sub valid_id { + my ($self, $c, $id) = @_; + my $source = $c->req->params; + my $type = $source->{type}; + unless($type){ + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Mandatory parameter 'type' missing in request"); + return; + } + return $self->SUPER::valid_id($c, $id); +} + +sub get_item_id{ + my($self, $c, $item, $resource, $form, $params) = @_; + my ($id, $type); + if('HASH' eq ref $item){ + $id = int($item->{id}); + $type = $item->{type}; + }elsif(blessed $item){ + $id = int($item->id); + $type = $item->type; + } + if(('HASH' eq ref $params) && 'hal_links_href' eq $params->{purpose}){ + return $id.'?type='.$type; + + } + return $id ; +} + sub _item_rs { - my ($self, $c, $params) = @_; + my ($self, $c, $owner, $params) = @_; $params //= $c->req->params; $params = { %$params }; - + my $schema = $c->model('DB'); my ($uuid,$contract_id,$reseller_id,$provider_id,$show); - (my $subscriber,$uuid) = $self->_get_subscriber($c,$params); - (my $contract,$contract_id) = $self->_get_contract($c,$params); - - if ($c->user->roles eq "subscriber") { - $uuid = $c->user->voip_subscriber->uuid; - } elsif ($c->user->roles eq "subscriberadmin") { - $contract_id = $c->user->account_id; - } elsif ($c->user->roles eq "reseller") { - $reseller_id = $c->user->reseller_id; - $provider_id = $c->user->reseller->contract_id; - } - - unless (defined $uuid or defined $contract_id) { - #die subscriber_id or customer_id required - } + $contract_id = $owner->{customer} ? $owner->{customer}->id : undef ; + $uuid = $owner->{subscriber} ? $owner->{subscriber}->uuid : undef; + $reseller_id = $owner->{customer} ? $owner->{customer}->contact->reseller_id : undef; + $provider_id = $owner->{customer} ? $owner->{customer}->contact->reseller->contract_id : undef; my $item_rs; my $type_param = ((exists $params->{type}) ? ($params->{type} // '') : undef); @@ -190,41 +277,6 @@ sub _item_rs { } -sub _get_subscriber { - my ($self, $c, $params) = @_; - $params //= $c->req->params; - my ($subscriber,$uuid); - if ($params->{subscriber_id}) { - eval { - $subscriber = $c->model('DB')->resultset('voip_subscribers')->find($params->{subscriber_id}); - }; - if ($subscriber) { - $uuid = $subscriber->uuid; - } else { - #die invalid subscriber_id '$params->{subscriber_id}' - } - } - return ($subscriber,$uuid); -} - -sub _get_contract { - my ($self, $c, $params) = @_; - $params //= $c->req->params; - my ($contract,$contract_id); - if ($params->{customer_id}) { - # ensure integer, allow terminated - eval { - $contract = $c->model('DB')->resultset('contracts')->find($params->{customer_id}); - }; - if ($contract) { - $contract_id = $contract->id; - } else { - #die invalid customer_id '$params->{customer_id}' - } - } - return ($contract,$contract_id); -} - sub _apply_timestamp_from_to { my $self = shift; my %params = @_; @@ -471,14 +523,14 @@ sub _get_sms_rs { $rs = $rs->search_rs({ 'contact.reseller_id' => $reseller_id, },{ - join => { provisioning_voip_subscriber => { subscriber => { contract => 'contact'} } }, + join => { provisioning_voip_subscriber => { voip_subscriber => { contract => 'contact'} } }, }); } if ($contract_id) { $rs = $rs->search({ 'contract.id' => $contract_id, },{ - join => { provisioning_voip_subscriber => { subscriber => 'contract' } }, + join => { provisioning_voip_subscriber => { voip_subscriber => 'contract' } }, }); } if ($uuid) { @@ -527,14 +579,14 @@ sub _get_fax_rs { $rs = $rs->search_rs({ 'contact.reseller_id' => $reseller_id, },{ - join => { provisioning_voip_subscriber => { subscriber => { contract => 'contact'} } }, + join => { provisioning_voip_subscriber => { voip_subscriber => { contract => 'contact'} } }, }); } if ($contract_id) { $rs = $rs->search({ 'contract.id' => $contract_id, },{ - join => { provisioning_voip_subscriber => { subscriber => 'contract' } }, + join => { provisioning_voip_subscriber => { voip_subscriber => 'contract' } }, }); } if ($uuid) { @@ -671,7 +723,6 @@ sub _get_as_list { #push(@as,($accessors[$i - 1] ? $accessors[$i - 1] : 'field'.$i)); } return @as; - } sub _get_alias { @@ -680,47 +731,118 @@ sub _get_alias { sub get_form { my ($self, $c) = @_; - return (NGCP::Panel::Form::get("NGCP::Panel::Form::Conversation::API", $c),['id']); + return (NGCP::Panel::Form::get("NGCP::Panel::Form::Conversation::API", $c),[qw/id call_id source_user_id source_account_id destination_user_id destination_account_id call_id subscriber_id voicemail_subscriber_id/]); } sub process_hal_resource { my($self, $c, $item, $resource, $form) = @_; - - use Data::Dumper; - #$c->log->debug(Dumper($item)); - #$c->log->debug(Dumper($resource)); - + my $schema = $c->model('DB'); + # todo: mashal specific fields, per conversation event type ... + my ($item_mock_obj, $item_accessors_hash) = _get_item_object($c, $item); + if('call' eq $item->{type}){ + my $cdr_subscriber_id = $c->model('DB')->resultset('voip_subscribers')->search_rs({ + 'uuid' => $item_mock_obj->source_user_id, + })->first->id; + my $cdr_customer_id = $item_mock_obj->source_account_id; + my $owner = NGCP::Panel::Utils::API::Calllist::get_owner_data($self, $c, $schema, { subscriber_id => $cdr_subscriber_id } ); + if(!$owner){ + return; + } + $resource = NGCP::Panel::Utils::CallList::process_cdr_item( + $c, + $item_mock_obj, + $owner, + ); + @{$resource}{qw/caller callee/} = @{$resource}{qw/own_cli other_cli/}; + }elsif('fax' eq $item->{type}){ + my $fax_subscriber = $c->model('DB')->resultset('provisioning_voip_subscribers')->search_rs({ + 'id' => $item_mock_obj->subscriber_id, + })->first->voip_subscriber; + $resource = NGCP::Panel::Utils::Fax::process_fax_journal_item($c, $item_mock_obj, $fax_subscriber); + foreach my $field (qw/type id status reason pages filename/){ + $resource->{$field} = $item_mock_obj->$field; + } + }elsif('voicemail' eq $item->{type}){ + $resource = $item_accessors_hash; + $resource->{caller} = $item_mock_obj->callerid; + $resource->{voicemail_subscriber_id} = $c->model('DB')->resultset('voicemail_spool')->search_rs({ + 'mailboxuser' => $item_mock_obj->mailboxuser, + })->first->mailboxuser->provisioning_voip_subscriber->voip_subscriber->id; + # type is last item of path like /var/spool/asterisk/voicemail/default/uuid/INBOX + my @p = split '/', $item_mock_obj->dir; + $resource->{folder} = pop @p; + }elsif('sms' eq $item->{type}){ + $resource = $item_accessors_hash; + }elsif('xmpp' eq $item->{type}){ + $resource = $item_accessors_hash; + } + $resource->{start_time} //= $item_mock_obj->timestamp; my $datetime_fmt = DateTime::Format::Strptime->new( pattern => '%F %T', ); - my $timestamp = NGCP::Panel::Utils::DateTime::epoch_local($resource->{timestamp}); + my $timestamp = NGCP::Panel::Utils::DateTime::epoch_local($resource->{start_time}); #if($c->req->param('tz') && DateTime::TimeZone->is_valid_name($c->req->param('tz'))) { # $timestamp->set_time_zone($c->req->param('tz')); #} - $resource->{timestamp} = $datetime_fmt->format_datetime($timestamp); - $resource->{timestamp} .= '.' . $timestamp->millisecond if $timestamp->millisecond > 0.0; + $resource->{start_time} = $datetime_fmt->format_datetime($timestamp); + $resource->{start_time} .= '.' . $timestamp->millisecond if $timestamp->millisecond > 0.0; - # todo: mashal specific fields, per conversation event type ... + return $resource; +} - if ('call' eq $resource->{type}) { - my $cdr_item = NGCP::Panel::Utils::CallList::process_cdr_item($c,_hash2obj( - classname => ref $cdr_proto, - hash => $resource, - private => { c => $c, }, - ),{ - subscriber => ($self->_get_subscriber($c))[0], - customer => ($self->_get_contract($c))[0], - }, - ); - # todo: populate $resource ... - $c->log->debug(Dumper($cdr_item)); +sub _get_fields_names{ + my ($fields, $fields_tied) = @_; + return { map { $_ => $fields_tied->Indices($_) ? _get_alias($fields_tied->Indices($_) + 1) : $_; } (keys %$fields, 'id','type','timestamp') }; +} + +sub _get_item_object{ + my($c, $item) = @_; + my ($fields, $fields_tied, $class_proto) = _get_fields_by_type($item->{type}); + + my $item_accessors_hash = {%$item}; + foreach(keys %$fields){ + $item_accessors_hash->{$_} = $item->{_get_alias($fields_tied->Indices($_) + 1)}; } - return $resource; + my $item_mock_obj = NGCP::Panel::Utils::Generic::hash2obj( + classname => ref $class_proto, + hash => $item_accessors_hash, + private => { c => $c, }, + ); + return ($item_mock_obj, $item_accessors_hash); +} + +sub _get_fields_by_type{ + my($type) = @_; + my ($fields, $fields_tied, $proto); + + if('call' eq $type){ + $fields = \%call_fields; + $fields_tied = $call_fields_tied; + $proto = $cdr_proto; + }elsif('voicemail' eq $type){ + $fields = \%voicemail_fields; + $fields_tied = $voicemail_fields_tied; + $proto = $voicemail_proto; + }elsif('sms' eq $type){ + $fields = \%sms_fields; + $fields_tied = $sms_fields_tied; + $proto = $sms_proto; + }elsif('fax' eq $type){ + $fields = \%fax_fields; + $fields_tied = $fax_fields_tied; + $proto = $fax_proto; + }elsif('xmpp' eq $type){ + $fields = \%xmpp_fields; + $fields_tied = $xmpp_fields_tied; + $proto = $xmpp_proto; + } + return $fields,$fields_tied,$proto; } sub hal_links { my($self, $c, $item, $resource, $form) = @_; + #$c->log->debug("hal_links: type=".($resource->{type} // 'undefined')."; id=".($resource->{id} // 'undefined').";"); return [ ('call' eq $resource->{type} ? NGCP::Panel::Utils::DataHalLink->new(relation => 'ngcp:calls', href => sprintf("/api/calls/%d", $resource->{id})) : ()), @@ -731,42 +853,10 @@ sub hal_links { ('fax' eq $resource->{type} ? NGCP::Panel::Utils::DataHalLink->new(relation => 'ngcp:faxes', href => sprintf("/api/faxes/%d", $resource->{id})) : ()), # todo - add xmpp mam rail: - # ('xmpp' eq $item->{type} ? + #('xmpp' eq $item->{type} ? # NGCP::Panel::Utils::DataHalLink->new(relation => 'ngcp:xmpp', href => sprintf("/api/xmpp/%d", $item->{id})) : ()), ]; } -sub _hash2obj { - my %params = @_; - my ($hash,$private,$classname,$accessors) = @params{qw/hash private classname accessors/}; - - my $obj; - $obj = $hash if 'HASH' eq ref $hash; - $obj //= {}; - $obj = { %$obj, %$private } if 'HASH' eq ref $private; - unless (defined $classname and length($classname) > 0) { - my @chars = ('A'..'Z'); - $classname //= ''; - $classname .= $chars[rand scalar @chars] for 1..8; - } - $classname = __PACKAGE__ . '::' . $classname unless $classname =~ /::/; - bless($obj,$classname); - no strict "refs"; - return $obj if scalar %{$classname . '::'}; - print "registering class $classname\n"; - $accessors //= {}; - foreach my $accessor (keys %$accessors) { - print "registering accessor $classname::$accessor\n"; - *{$classname . '::' . $accessor} = sub { - my $self = shift; - return &{$accessors->{$accessor}}($self,@_); - } if 'CODE' eq ref $accessors->{$accessor}; - *{$classname . '::' . $accessor} = sub { - my $self = shift; - return $self->{$accessors->{$accessor}}; - } if '' eq ref $accessors->{$accessor}; - } - return $obj; -} 1; diff --git a/lib/NGCP/Panel/Role/Entities.pm b/lib/NGCP/Panel/Role/Entities.pm index 5270cc75bc..1e003efd49 100644 --- a/lib/NGCP/Panel/Role/Entities.pm +++ b/lib/NGCP/Panel/Role/Entities.pm @@ -44,11 +44,6 @@ sub config_allowed_roles { return [qw/admin reseller/]; } -sub get_list{ - my ($self, $c) = @_; - return $self->item_rs($c); -} - sub get { my ($self, $c) = @_; my $header_accept = $c->request->header('Accept'); @@ -60,6 +55,7 @@ sub get { my $rows = $c->request->params->{rows} // 10; { my $items = $self->get_list($c); + return unless $items; (my $total_count, $items) = $self->paginate_order_collection($c, $items); my (@embedded, @links); my ($form) = $self->get_form($c); @@ -68,7 +64,8 @@ sub get { push @embedded, $self->hal_from_item($c, $item, $form); push @links, NGCP::Panel::Utils::DataHalLink->new( relation => 'ngcp:'.$self->resource_name, - href => sprintf('/%s%s', $c->request->path, $self->get_item_id($c, $item)), + href => sprintf('/%s%s', $c->request->path, $self->get_item_id($c, + $item, undef, undef, { purpose => 'hal_links_href' })), ); } push @links, diff --git a/lib/NGCP/Panel/Role/EntitiesItem.pm b/lib/NGCP/Panel/Role/EntitiesItem.pm index 527efdfb4b..c676168334 100644 --- a/lib/NGCP/Panel/Role/EntitiesItem.pm +++ b/lib/NGCP/Panel/Role/EntitiesItem.pm @@ -55,8 +55,8 @@ sub get { my $item = $self->item_by_id_valid($c, $id); last unless $item; my $header_accept = $c->request->header('Accept'); - if(defined $header_accept - && ($header_accept ne 'application/json') + if(defined $header_accept + && ($header_accept ne 'application/json') && ($header_accept ne '*/*') ) { $self->return_requested_type($c,$id,$item); @@ -64,7 +64,7 @@ sub get { } my $hal = $self->hal_from_item($c, $item); - + return unless $hal; 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-[a-z]+)"|rel="item $1"|r =~ @@ -110,11 +110,11 @@ sub patch { $self->complete_transaction($c); $self->post_process_commit($c, 'patch', $item, $old_resource, $resource, $form, $process_extras); - $self->return_representation($c, - 'item' => $item, - 'form' => $form, - 'preference' => $preference, - 'form_exceptions' => $form_exceptions + $self->return_representation($c, + 'item' => $item, + 'form' => $form, + 'preference' => $preference, + 'form_exceptions' => $form_exceptions ); } return; @@ -152,11 +152,11 @@ sub put { $self->complete_transaction($c); $self->post_process_commit($c, 'put', $item, $old_resource, $resource, $form, $process_extras); - $self->return_representation($c, - 'item' => $item, - 'form' => $form, - 'preference' => $preference, - 'form_exceptions' => $form_exceptions + $self->return_representation($c, + 'item' => $item, + 'form' => $form, + 'preference' => $preference, + 'form_exceptions' => $form_exceptions ); } return; diff --git a/lib/NGCP/Panel/Utils/API/Calllist.pm b/lib/NGCP/Panel/Utils/API/Calllist.pm new file mode 100644 index 0000000000..c52cf54e04 --- /dev/null +++ b/lib/NGCP/Panel/Utils/API/Calllist.pm @@ -0,0 +1,111 @@ +package NGCP::Panel::Utils::API::Calllist; +use strict; +use warnings; + +use HTTP::Status qw(:constants); + + +sub get_owner_data { + my ($self, $c, $schema, $source) = @_; + + my $ret; + $source //= $c->req->params; + my $src_subscriber_id = $source->{subscriber_id}; + my $src_customer_id = $source->{customer_id}; + + if($c->user->roles eq "admin" || $c->user->roles eq "reseller") { + if($src_subscriber_id) { + my $sub = $schema->resultset('voip_subscribers')->find($src_subscriber_id); + unless($sub) { + $self->error($c, HTTP_NOT_FOUND, "Invalid 'subscriber_id'."); + return; + } + if($c->user->roles eq "reseller" && $sub->contract->contact->reseller_id != $c->user->reseller_id) { + $self->error($c, HTTP_NOT_FOUND, "Invalid 'subscriber_id'."); + return; + } + return { + subscriber => $sub, + customer => $sub->contract, + }; + } elsif($src_customer_id) { + my $cust = $schema->resultset('contracts')->find($src_customer_id); + unless($cust && $cust->contact->reseller_id) { + $self->error($c, HTTP_NOT_FOUND, "Invalid 'customer_id'."); + return; + } + if($c->user->roles eq "reseller" && $cust->contact->reseller_id != $c->user->reseller_id) { + $self->error($c, HTTP_NOT_FOUND, "Invalid 'customer_id'."); + return; + } + return { + subscriber => undef, + customer => $cust, + }; + } else { + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Mandatory parameter 'subscriber_id' or 'customer_id' missing in request"); + return; + } + } elsif($c->user->roles eq "subscriberadmin") { + if($src_subscriber_id) { + my $sub = $schema->resultset('voip_subscribers')->find($src_subscriber_id); + unless($sub) { + $self->error($c, HTTP_NOT_FOUND, "Invalid 'subscriber_id'."); + return; + } + if($sub->contract_id != $c->user->account_id) { + $self->error($c, HTTP_NOT_FOUND, "Invalid 'subscriber_id'."); + return; + } + return { + subscriber => $sub, + customer => $sub->contract, + }; + } else { + my $cust = $schema->resultset('contracts')->find($c->user->account_id); + unless($cust && $cust->contact->reseller_id) { + $self->error($c, HTTP_NOT_FOUND, "Invalid 'customer_id'."); + return; + } + return { + subscriber => undef, + customer => $cust, + }; + } + } elsif($c->user->roles eq "subscriber") { + return { + subscriber => $c->user->voip_subscriber, + customer => $c->user->voip_subscriber->contract, + }; + } else { + $self->error($c, HTTP_NOT_FOUND, "Unknown role '".$c->user->roles."' of the user."); + return; + } +} +1; + +=head1 NAME + +NGCP::Panel::Utils::API::Calllist + +=head1 DESCRIPTION + +A temporary helper to manipulate calls related data in REST API modules + +=head1 METHODS + +=head2 get_owner_data + +Check if mandatory calls list parameters customer_id or subscriber_id are presented and get proper objects. + +=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/Utils/CallList.pm b/lib/NGCP/Panel/Utils/CallList.pm index f82c25c6f0..61e03bb933 100644 --- a/lib/NGCP/Panel/Utils/CallList.pm +++ b/lib/NGCP/Panel/Utils/CallList.pm @@ -49,6 +49,8 @@ sub process_cdr_item { $params //= $c->req->params; $resource->{call_id} = $item->call_id; + $resource->{id} = $item->id; + $resource->{call_type} = $item->call_type; my $intra = 0; if($item->source_user_id && $item->source_account_id == $item->destination_account_id) { @@ -284,7 +286,7 @@ sub process_cdr_item { $resource->{duration} = NGCP::Panel::Utils::DateTime::sec_to_hms($c,$item->duration,3); $resource->{customer_cost} = $resource->{direction} eq "out" ? $item->source_customer_cost : $item->destination_customer_cost; - if ($cust->add_vat) { + if (defined $cust && $cust->add_vat) { $resource->{total_customer_cost} = $resource->{customer_cost} * (1 + $cust->vat_rate / 100); } else { $resource->{total_customer_cost} = $resource->{customer_cost}; diff --git a/lib/NGCP/Panel/Utils/Generic.pm b/lib/NGCP/Panel/Utils/Generic.pm index 4f275fb711..6fc8eebe88 100644 --- a/lib/NGCP/Panel/Utils/Generic.pm +++ b/lib/NGCP/Panel/Utils/Generic.pm @@ -7,9 +7,9 @@ use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); $VERSION = 1.00; @ISA = qw(Exporter); @EXPORT = (); -@EXPORT_OK = qw(is_int is_integer is_decimal merge compare is_false is_true get_inflated_columns_all); +@EXPORT_OK = qw(is_int is_integer is_decimal merge compare is_false is_true get_inflated_columns_all hash2obj); %EXPORT_TAGS = ( DEFAULT => [qw(&is_int &is_integer &is_decimal &merge &compare &is_false &is_true)], - all => [qw(&is_int &is_integer &is_decimal &merge &compare &is_false &is_true &get_inflated_columns_all)]); + all => [qw(&is_int &is_integer &is_decimal &merge &compare &is_false &is_true &get_inflated_columns_all &hash2obj)]); use Hash::Merge; use Data::Compare qw//; @@ -105,4 +105,37 @@ sub get_inflated_columns_all{ #return [ map { { $_->get_inflated_columns }; } $rs->all ]; } +sub hash2obj { + my %params = @_; + my ($hash,$private,$classname,$accessors) = @params{qw/hash private classname accessors/}; + + my $obj; + $obj = $hash if 'HASH' eq ref $hash; + $obj //= {}; + $obj = { %$obj, %$private } if 'HASH' eq ref $private; + unless (defined $classname and length($classname) > 0) { + my @chars = ('A'..'Z'); + $classname //= ''; + $classname .= $chars[rand scalar @chars] for 1..8; + } + $classname = __PACKAGE__ . '::' . $classname unless $classname =~ /::/; + bless($obj,$classname); + no strict "refs"; + return $obj if scalar %{$classname . '::'}; + print "registering class $classname\n"; + $accessors //= {}; + foreach my $accessor (keys %$accessors) { + print "registering accessor $classname::$accessor\n"; + *{$classname . '::' . $accessor} = sub { + my $self = shift; + return &{$accessors->{$accessor}}($self,@_); + } if 'CODE' eq ref $accessors->{$accessor}; + *{$classname . '::' . $accessor} = sub { + my $self = shift; + return $self->{$accessors->{$accessor}}; + } if '' eq ref $accessors->{$accessor}; + } + return $obj; +} + 1;