diff --git a/lib/NGCP/Panel/Controller/API/CallQueues.pm b/lib/NGCP/Panel/Controller/API/CallQueues.pm new file mode 100644 index 0000000000..6f9a1dc236 --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/CallQueues.pm @@ -0,0 +1,61 @@ +package NGCP::Panel::Controller::API::CallQueues; +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); + +sub allowed_methods{ + return [qw/GET OPTIONS HEAD/]; +} + +sub api_description { + return 'The queue of waiting calls per subscriber.'; +}; + +sub query_params { + return [ + { + param => 'reseller_id', + description => 'Filter for callqueues of subscribers belonging to a specific reseller', + query => { + first => sub { + my $q = shift; + { 'domain_resellers.reseller_id' => $q }; + }, + second => sub { + { join => 'domain_resellers' }; + }, + }, + }, + { + # we handle that separately/manually in the role + param => 'number', + description => 'Filter for callqueues of subscribers with numbers matching the given pattern.', + }, + ]; +} + +use parent qw/NGCP::Panel::Role::Entities NGCP::Panel::Role::API::CallQueues/; + +sub resource_name{ + return 'callqueues'; +} + +sub dispatch_path{ + return '/api/callqueues/'; +} + +sub relation{ + return 'http://purl.org/sipwise/ngcp-api/#rel-callqueues'; +} + +__PACKAGE__->set_config({ + allowed_roles => [qw/admin reseller subscriberadmin subscriber/], +}); + +1; diff --git a/lib/NGCP/Panel/Controller/API/CallQueuesItem.pm b/lib/NGCP/Panel/Controller/API/CallQueuesItem.pm new file mode 100644 index 0000000000..c55e39c0b5 --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/CallQueuesItem.pm @@ -0,0 +1,35 @@ +package NGCP::Panel::Controller::API::CallQueuesItem; +use NGCP::Panel::Utils::Generic qw(:all); + +use Sipwise::Base; + +use HTTP::Headers qw(); +use HTTP::Status qw(:constants); + +require Catalyst::ActionRole::ACL; +require NGCP::Panel::Role::HTTPMethods; +require Catalyst::ActionRole::RequireSSL; + +sub allowed_methods{ + return [qw/GET OPTIONS HEAD/]; +} + +use parent qw/NGCP::Panel::Role::EntitiesItem NGCP::Panel::Role::API::CallQueues/; + +sub resource_name{ + return 'callqueues'; +} + +sub dispatch_path{ + return '/api/callqueues/'; +} + +sub relation{ + return 'http://purl.org/sipwise/ngcp-api/#rel-callqueues'; +} + +__PACKAGE__->set_config({ + allowed_roles => [qw/admin reseller subscriberadmin subscriber/], +}); + +1; diff --git a/lib/NGCP/Panel/Form/CallQueue/API.pm b/lib/NGCP/Panel/Form/CallQueue/API.pm new file mode 100644 index 0000000000..f113ad8532 --- /dev/null +++ b/lib/NGCP/Panel/Form/CallQueue/API.pm @@ -0,0 +1,32 @@ +package NGCP::Panel::Form::CallQueue::API; +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler'; + +has_field 'id' => ( + type => 'Hidden', + noupdate => 1, +); + +has_field 'queue_length' => ( + type => 'Integer', + required => 0, + default => '0', + element_attr => { + rel => ['tooltip'], + title => ['The length of the call queue.'] + }, +); + +has_field 'queue' => ( + type => 'Repeatable', + do_wrapper => 1, + do_label => 0, + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['Number of Objects, each containing the keys ' . + '"start_time", "duration", "callee" and "caller".'], + }, +); + +1; diff --git a/lib/NGCP/Panel/Role/API/CallQueues.pm b/lib/NGCP/Panel/Role/API/CallQueues.pm new file mode 100644 index 0000000000..b3b33ca34a --- /dev/null +++ b/lib/NGCP/Panel/Role/API/CallQueues.pm @@ -0,0 +1,200 @@ +package NGCP::Panel::Role::API::CallQueues; +use NGCP::Panel::Utils::Generic qw(:all); + +use Sipwise::Base; + +use parent 'NGCP::Panel::Role::API'; + +use boolean qw(true); +use Data::HAL qw(); +use Data::HAL::Link qw(); + +my $redis_callqueue_key_prefix = 'callqueue:'; +my $redis_dialogdata_key_prefix = 'dialog:cid::'; +my $number_search_limit = 100; # scan redis only if collection gets bigger than this + +# todo: move this stash factory method below to some util. +sub _get_redis { + my ($self, $c, $select) = @_; + my $stash_key = 'redis'; + if (defined $select) { + $stash_key .= '_' . $select; + } else { + $c->log->error("redis store not specified"); + return; + } + my $redis = $c->stash->{$stash_key}; + unless ($redis) { + try { + $redis = Redis->new( + server => $c->config->{redis}->{central_url}, + reconnect => 10, every => 500000, # 500ms + cnx_timeout => 3, + ); + unless ($redis) { + $c->log->error("Failed to connect to central redis url " . $c->config->{redis}->{central_url}); + return; + } + $redis->select($select) if defined $select; + $c->stash($stash_key => $redis); + } catch($e) { + $c->log->error("Failed to fetch callqueue information from redis: $e"); + return; + } + } + return $redis; +} + +sub _item_rs { + my ($self, $c, $id) = @_; + + #$c->config->{redis}->{callqueue_db} = 3; + my $redis = $self->_get_redis($c,$c->config->{redis}->{callqueue_db}); + + my $rs = $c->model('DB')->resultset('voip_subscribers'); + if($c->user->roles eq "admin") { + } elsif($c->user->roles eq "reseller") { + $rs = $rs->search_rs({ + 'contact.reseller_id' => $c->user->reseller_id, + },{ + join => { 'contract' => 'contact' } + }); + } elsif ($c->user->roles eq "subscriberadmin") { + $rs = $rs->search_rs({ + 'me.contract_id' => $c->user->account_id, + }); + } elsif ($c->user->roles eq "subscriber") { + $rs = $rs->search_rs({ + 'me.uuid' => $c->user->uuid, + }); + } + + if ($id) { + my $subs = $rs->find($id); + if ($subs) { + $rs = $rs->search_rs({ + 'me.uuid' => ($redis->exists($redis_callqueue_key_prefix . $subs->uuid) ? $subs->uuid : -1), + }); + } + } else { + my $callqueue_uuids = $c->stash->{callqueue_uuids}; + if ($c->req->params->{number}) { + $rs = $rs->search_rs({ + 'voip_dbaliases.username' => { like => $c->req->params->{number} }, + },{ + join => { 'provisioning_voip_subscriber' => 'voip_dbaliases' }, + distinct => 1, + }); + if (not $callqueue_uuids and $rs->search_rs(undef,{ rows => $number_search_limit, })->count < $number_search_limit) { + $callqueue_uuids = [ -1 ]; + for my $subs ($rs->all) { + push(@$callqueue_uuids,$subs->uuid) if $redis->exists($redis_callqueue_key_prefix . $subs->uuid); + } + $c->stash(callqueue_uuids => $callqueue_uuids); + } + } + unless ($callqueue_uuids) { + $callqueue_uuids = [ -1 ]; + my $cursor = 0; + do { + my $res = $redis->scan($cursor, MATCH => $redis_callqueue_key_prefix . '*', COUNT => 1000); + $cursor = shift @$res; + my $mapkeys = shift @$res; + foreach my $mapkey (@$mapkeys) { + push(@$callqueue_uuids,$mapkey =~ s/^$redis_callqueue_key_prefix//r); + } + } while ($cursor); + $c->stash(callqueue_uuids => $callqueue_uuids); + } + $rs = $rs->search_rs({ + 'me.uuid' => { -in => $callqueue_uuids }, + }); + } + + return $rs; +} + +sub get_form { + my ($self, $c) = @_; + return NGCP::Panel::Form::get("NGCP::Panel::Form::CallQueue::API", $c); +} + +sub hal_links { + my($self, $c, $item, $resource, $form) = @_; + return [ + Data::HAL::Link->new(relation => 'ngcp:subscribers', href => sprintf("/api/subscribers/%d", $item->id)), + ]; +} + +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("/api/%s/", $self->resource_name)), + Data::HAL::Link->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'), + Data::HAL::Link->new(relation => 'self', href => sprintf("%s%d", $self->dispatch_path, $item->id)), + Data::HAL::Link->new(relation => 'ngcp:subscribers', href => sprintf("/api/subscribers/%d", $item->id)), + ], + relation => 'ngcp:'.$self->resource_name, + ); + + my $resource = $self->resource_from_item($c, $item, $form); + $hal->resource($resource); + return $hal; +} + +sub resource_from_item { + my ($self, $c, $item, $form) = @_; + + my %resource = (); + + #$c->config->{redis}->{callqueue_db} = 3; + #$c->config->{redis}->{dialog_db} = 23; + + my $redis = $self->_get_redis($c,$c->config->{redis}->{callqueue_db}); + my $res = $redis->lrange($redis_callqueue_key_prefix . $item->uuid, 0, -1); + $redis = $self->_get_redis($c,$c->config->{redis}->{dialog_db}); + my @callqueue = (); + if ($res) { + foreach my $call_id (@$res) { + my $queue_item = { + duration => undef, + caller => undef, + callee => undef, + start_time => undef, + call_id => $call_id, + }; + my $dialogentry_keys = $redis->smembers($redis_dialogdata_key_prefix . $call_id); + #use Data::Dumper; + #$c->log->debug($redis_dialogdata_key_prefix . $call_id . ' -> ' . Dumper($dialogentry_keys)); + if ($dialogentry_keys) { + foreach my $dialogentry_key (@$dialogentry_keys) { + my %dialogdata = $redis->hgetall($dialogentry_key); + if ($dialogdata{start_time}) { + my $duration = time() - $dialogdata{start_time}; + if (not defined $queue_item->{start_time} or $dialogdata{start_time} < $queue_item->{duration}) { + $queue_item->{duration} = time() - $dialogdata{start_time}; + $queue_item->{caller} = $dialogdata{from_uri}; + $queue_item->{start_time} = $dialogdata{start_time}; + $queue_item->{callee} = $dialogdata{to_uri}; + } + } + } + } + push(@callqueue,$queue_item); + } + } + $resource{queue} = \@callqueue; + $resource{queue_length} = scalar @callqueue; + + return \%resource; +} + +1;