TT#37205 bnumber cf in API: complex /api/cf*

* /api/cfmappings
* /api/cfbnumbersets

small fix: "anonymous" doesn't make sense for b-numbers

Change-Id: I13fe97bb35c7c34f8919932391dea86bd492a291
changes/70/22270/8
Gerhard Jungwirth 8 years ago
parent c72c74b174
commit f5687b2294

@ -0,0 +1,106 @@
package NGCP::Panel::Controller::API::CFBNumberSets;
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 POST OPTIONS HEAD/];
}
sub api_description {
return 'Defines a collection of CallForward B-Number Sets, including the bnumbers, which can be set '.
'to define CallForwards using <a href="#cfmappings">CFMappings</a>.',;
}
sub query_params {
return [
{
param => 'subscriber_id',
description => 'Filter for B-Number sets belonging to a specific subscriber',
query => {
first => sub {
my $q = shift;
return { 'voip_subscriber.id' => $q };
},
second => sub {
return { join => {subscriber => 'voip_subscriber'}};
},
},
},
{
param => 'name',
description => 'Filter for items matching a B-Number Set name pattern',
query => {
first => sub {
my $q = shift;
{ name => { like => $q } };
},
second => sub {},
},
},
];
}
sub documentation_sample {
return {
subscriber_id => 20,
name => 'to_austria',
bnumbers => [{bnumber => '43*'}],
};
}
use parent qw/NGCP::Panel::Role::Entities NGCP::Panel::Role::API::CFBNumberSets/;
__PACKAGE__->set_config({
allowed_roles => [qw/admin reseller subscriberadmin subscriber/],
});
sub create_item {
my ($self, $c, $resource, $form, $process_extras) = @_;
my $schema = $c->model('DB');
my $bset;
try {
# no checks, they are in check_resource
my $b_subscriber = $schema->resultset('voip_subscribers')->find($resource->{subscriber_id});
my $subscriber = $b_subscriber->provisioning_voip_subscriber;
$bset = $schema->resultset('voip_cf_bnumber_sets')->create({
name => $resource->{name},
mode => $resource->{mode},
subscriber_id => $subscriber->id,
});
for my $s ( @{$resource->{bnumbers}} ) {
$bset->create_related("voip_cf_bnumbers", {
bnumber => $s->{bnumber},
});
last unless $self->add_create_journal_item_hal($c,sub {
my $self = shift;
my ($c) = @_;
my $_bset = $self->item_by_id($c, $bset->id);
return $self->hal_from_item($c, $_bset); });
}
} catch($e) {
$c->log->error("failed to create cfbnumberset: $e");
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create cfbnumberset.");
return;
}
return $bset;
}
# sub POST :Allow {
# }
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,42 @@
package NGCP::Panel::Controller::API::CFBNumberSetsItem;
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 NGCP::Panel::Utils::ValidateJSON qw();
require Catalyst::ActionRole::ACL;
require NGCP::Panel::Role::HTTPMethods;
require Catalyst::ActionRole::RequireSSL;
sub allowed_methods{
return [qw/GET OPTIONS HEAD PATCH PUT DELETE/];
}
use parent qw/NGCP::Panel::Role::EntitiesItem NGCP::Panel::Role::API::CFBNumberSets/;
sub journal_query_params {
my($self,$query_params) = @_;
return $self->get_journal_query_params($query_params);
}
__PACKAGE__->set_config({
allowed_roles => {
Default => [qw/admin reseller subscriberadmin subscriber/],
Journal => [qw/admin reseller/],
},
PATCH => { ops => [qw/add replace remove copy/] },
});
sub get_journal_methods{
return [qw/handle_item_base_journal handle_journals_get handle_journalsitem_get handle_journals_options handle_journalsitem_options handle_journals_head handle_journalsitem_head/];
}
1;
# vim: set tabstop=4 expandtab:

@ -17,7 +17,8 @@ sub allowed_methods{
sub api_description {
return 'Specifies callforward mappings of a subscriber, where multiple mappings can be specified per type (cfu, cfb, cft, cfna, cfs) ' .
'Each mapping consists of a destinationset name (see <a href="#cfdestinationsets">CFDestinationSets</a>), a timeset name ' .
'(see <a href="#cftimesets">CFTimeSets</a>) and a sourceset name (see <a href="#cfsourcesets">CFSourceSets</a>).';
'(see <a href="#cftimesets">CFTimeSets</a>), a sourceset name (see <a href="#cfsourcesets">CFSourceSets</a>), ' .
'and a bnumberset name (see <a href="#cfbnumbersets">CFBnumberSets</a>).';
}
sub query_params {

@ -0,0 +1,16 @@
package NGCP::Panel::Form::CallForward::CFBNumberSetAPI;
use HTML::FormHandler::Moose;
extends 'NGCP::Panel::Form::CallForward::CFBNumberSetSubAPI';
has_field 'subscriber_id' => (
type => 'PosInteger',
required => 1,
element_attr => {
rel => ['tooltip'],
title => ['The subscriber id this b-number set belongs to']
},
);
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,55 @@
package NGCP::Panel::Form::CallForward::CFBNumberSetSubAPI;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler';
has_field 'id' => (
type => 'Hidden',
);
has_field 'name' => (
type => 'Text',
required => 1,
element_attr => {
rel => ['tooltip'],
title => ['The name of the bnumber set']
},
);
has_field 'mode' => (
type => 'Select',
options => [
{value => 'whitelist', label => 'whitelist'},
{value => 'blacklist', label => 'blacklist'},
],
required => 1,
element_attr => {
rel => ['tooltip'],
title => ['The bnumber set mode. A blacklist matches everything except numbers in the list, a whitelist only matches numbers (or expressions) in this list.']
},
);
has_field 'bnumbers' => (
type => 'Repeatable',
element_attr => {
rel => ['tooltip'],
title => ['An array of bnumbers, each containing the key "bnumber" ' .
'which will be matched against the called party number (callee) to determine ' .
'whether to apply the callforward or not. ' .
'"bnumber" is the callee\'s number in E164 format to match. ' .
'Shell patterns like 431* or 49123~[1-5~]67 are possible.',
],
},
);
has_field 'bnumbers.id' => (
type => 'Hidden',
);
has_field 'bnumbers.bnumber' => (
type => 'Text',
label => 'B-Number',
);
1;
# vim: set tabstop=4 expandtab:

@ -106,6 +106,17 @@ has_field 'cfu.sourceset_id' => (
do_label => 0,
);
has_field 'cfu.bnumberset' => (
type => 'Text',
do_wrapper => 1,
do_label => 0,
);
has_field 'cfu.bnumberset_id' => (
type => 'PosInteger',
do_label => 0,
);
has_field 'cfb.destinationset' => (
type => 'Text',
do_wrapper => 1,
@ -139,6 +150,17 @@ has_field 'cfb.sourceset_id' => (
do_label => 0,
);
has_field 'cfb.bnumberset' => (
type => 'Text',
do_wrapper => 1,
do_label => 0,
);
has_field 'cfb.bnumberset_id' => (
type => 'PosInteger',
do_label => 0,
);
has_field 'cft.destinationset' => (
type => 'Text',
do_wrapper => 1,
@ -172,6 +194,17 @@ has_field 'cft.sourceset_id' => (
do_label => 0,
);
has_field 'cft.bnumberset' => (
type => 'Text',
do_wrapper => 1,
do_label => 0,
);
has_field 'cft.bnumberset_id' => (
type => 'PosInteger',
do_label => 0,
);
has_field 'cfna.destinationset' => (
type => 'Text',
do_wrapper => 1,
@ -205,6 +238,17 @@ has_field 'cfna.sourceset_id' => (
do_label => 0,
);
has_field 'cfna.bnumberset' => (
type => 'Text',
do_wrapper => 1,
do_label => 0,
);
has_field 'cfna.bnumberset_id' => (
type => 'PosInteger',
do_label => 0,
);
has_field 'cfs.destinationset' => (
type => 'Text',
do_wrapper => 1,
@ -238,6 +282,17 @@ has_field 'cfs.sourceset_id' => (
do_label => 0,
);
has_field 'cfs.bnumberset' => (
type => 'Text',
do_wrapper => 1,
do_label => 0,
);
has_field 'cfs.bnumberset_id' => (
type => 'PosInteger',
do_label => 0,
);
has_field 'cft_ringtimeout' => (
type => 'PosInteger',
do_wrapper => 1,

@ -59,8 +59,7 @@ has_field 'bnumbers.number' => (
element_attr => {
rel => ['tooltip'],
title => ['Matches the B-Number (original number dialled by the caller) in E164 format. ' .
'Shell patterns like 431* or 49123~[1-5~]67 are possible. ' .
'Use &quot;anonymous&quot; to match suppressed numbers.'],
'Shell patterns like 431* or 49123~[1-5~]67 are possible.'],
},
);

@ -0,0 +1,179 @@
package NGCP::Panel::Role::API::CFBNumberSets;
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();
use HTTP::Status qw(:constants);
use JSON::Types;
use NGCP::Panel::Utils::Subscriber;
use NGCP::Panel::Form;
sub resource_name {
return 'cfbnumbersets';
}
sub get_form {
my ($self, $c) = @_;
if($c->user->roles eq "subscriber") {
return NGCP::Panel::Form::get("NGCP::Panel::Form::CallForward::CFBNumberSetSubAPI", $c);
} elsif($c->user->roles eq "subscriberadmin") {
return NGCP::Panel::Form::get("NGCP::Panel::Form::CallForward::CFBNumberSetSubAPI", $c);
} else {
return NGCP::Panel::Form::get("NGCP::Panel::Form::CallForward::CFBNumberSetAPI", $c);
}
}
sub hal_links{
my($self, $c, $item, $resource, $form) = @_;
my $adm = $c->user->roles eq "admin" || $c->user->roles eq "reseller";
return [
Data::HAL::Link->new(relation => "ngcp:subscribers", href => sprintf("/api/subscribers/%d", $resource->{subscriber_id})),
$adm ? $self->get_journal_relation_link($item->id) : (),
];
}
sub _item_rs {
my ($self, $c) = @_;
my $item_rs;
if($c->user->roles eq "admin") {
$item_rs = $c->model('DB')->resultset('voip_cf_bnumber_sets');
} elsif ($c->user->roles eq "reseller") {
my $reseller_id = $c->user->reseller_id;
$item_rs = $c->model('DB')->resultset('voip_cf_bnumber_sets')
->search_rs({
'reseller_id' => $reseller_id,
} , {
join => {'subscriber' => {'contract' => 'contact'} },
});
# TODO: do we want subscriberadmins to update other subs' entries?
} elsif($c->user->roles eq "subscriberadmin" || $c->user->roles eq "subscriber") {
$item_rs = $c->model('DB')->resultset('voip_cf_bnumber_sets')
->search_rs({
'subscriber_id' => $c->user->id,
});
}
return $item_rs;
}
sub resource_from_item {
my ($self, $c, $item, $form) = @_;
my $resource = { $item->get_inflated_columns };
my $psub = $item->subscriber;
if ($psub && $psub->voip_subscriber) {
$resource->{subscriber_id} = int($psub->voip_subscriber->id);
} else {
delete $resource->{subscriber_id};
}
my @bnumbers;
for my $dest ($item->voip_cf_bnumbers->all) {
push @bnumbers, { $dest->get_inflated_columns, };
delete @{$bnumbers[-1]}{'bnumber_set_id', 'id'};
}
$resource->{bnumbers} = \@bnumbers;
return $resource;
}
sub check_resource {
my($self, $c, $item, $old_resource, $resource, $form) = @_;
my $schema = $c->model('DB');
if($c->user->roles eq "subscriberadmin" || $c->user->roles eq "subscriber") {
$resource->{subscriber_id} = $c->user->voip_subscriber->id;
} elsif(!defined $resource->{subscriber_id}) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Missing mandatory field 'subscriber_id'");
return;
}
my $b_subscriber = $schema->resultset('voip_subscribers')->find({
id => $resource->{subscriber_id},
});
unless($b_subscriber) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'subscriber_id'.");
return;
}
my $subscriber = $b_subscriber->provisioning_voip_subscriber;
unless($subscriber) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid subscriber.");
return;
}
if (! exists $resource->{bnumbers} ) {
$resource->{bnumbers} = [];
}
if (ref $resource->{bnumbers} ne "ARRAY") {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid field 'bnumbers'. Must be an array.");
return;
}
return 1; # all good
}
sub update_item {
my ($self, $c, $item, $old_resource, $resource, $form) = @_;
delete $resource->{id};
my $schema = $c->model('DB');
return unless $self->validate_form(
c => $c,
form => $form,
resource => $resource,
);
return unless $self->check_resource($c, $item, $old_resource, $resource, $form);
# no checks, they are in check_resource, disadvantage: subscriber is searched twice
my $b_subscriber = $schema->resultset('voip_subscribers')->find($resource->{subscriber_id});
my $subscriber = $b_subscriber->provisioning_voip_subscriber;
try {
$item->update({
name => $resource->{name},
mode => $resource->{mode},
subscriber_id => $subscriber->id,
})->discard_changes;
$item->voip_cf_bnumbers->delete;
for my $s ( @{$resource->{bnumbers}} ) {
$item->create_related("voip_cf_bnumbers", {
bnumber => $s->{bnumber},
});
}
$item->discard_changes;
die unless $self->add_update_journal_item_hal($c,sub {
my ($self, $c) = @_;
return $self->hal_from_item($c, $item);
});
} catch($e) {
$c->log->error("failed to create cfbnumberset: $e");
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create cfbnumberset.");
return;
};
return $item;
}
sub post_process_commit {
my($self, $c, $action, $item) = @_;
if ($action eq 'delete') {
$self->add_delete_journal_item_hal($c,sub {
my $self = shift;
my ($c) = @_;
return $self->hal_from_item($c, $item); });
}
return;
}
1;
# vim: set tabstop=4 expandtab:

@ -55,6 +55,13 @@ sub hal_from_item {
sourceset => undef,
sourceset_id => undef,
),
$mapping->bnumber_set ? (
bnumberset => $mapping->bnumber_set->name,
bnumberset_id => $mapping->bnumber_set->id,
) : (
bnumberset => undef,
bnumberset_id => undef,
),
};
}
@ -145,6 +152,7 @@ sub update_item {
my $dsets_rs = $c->model('DB')->resultset('voip_cf_destination_sets');
my $tsets_rs = $c->model('DB')->resultset('voip_cf_time_sets');
my $ssets_rs = $c->model('DB')->resultset('voip_cf_source_sets');
my $bsets_rs = $c->model('DB')->resultset('voip_cf_bnumber_sets');
for my $type ( qw/cfu cfb cft cfna cfs/) {
if (ref $resource->{$type} ne "ARRAY") {
@ -213,10 +221,30 @@ sub update_item {
return;
}
my $bset; my $has_bset;
if (defined $mapping->{bnumberset_id}) {
$bset = $bsets_rs->find({
subscriber_id => $p_subs_id,
id => $mapping->{bnumberset_id},
});
$has_bset = 1;
} elsif (defined $mapping->{bnumberset}) {
$bset = $bsets_rs->find({
subscriber_id => $p_subs_id,
name => $mapping->{bnumberset},
});
$has_bset = 1;
}
if($has_bset && !$bset) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'bnumberset'. Could not be found.");
return;
}
push @new_mappings, $mappings_rs->new_result({
destination_set_id => $dset->id,
time_set_id => $tset ? $tset->id : undef,
source_set_id => $sset ? $sset->id : undef,
bnumber_set_id => $bset ? $bset->id : undef,
type => $type,
});
}

@ -58,6 +58,7 @@ $ua = Test::Collection->new()->ua();
calls => 1,
capabilities => 1,
ccmapentries => 1,
cfbnumbersets => 1,
cfdestinationsets => 1,
cfmappings => 1,
cfsourcesets => 1,

Loading…
Cancel
Save