TT#17638 Rewrite Rule API to set priority

Change-Id: I84099ab6d4869ee232532668fa15f1cc40501fb8
changes/16/14316/14
Irina Peshinskaya 8 years ago
parent 90340caae0
commit c0940df9db

@ -1,22 +1,21 @@
package NGCP::Panel::Controller::API::RewriteRuleSets;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use parent qw/NGCP::Panel::Role::Entities NGCP::Panel::Role::API::RewriteRuleSets/;
use boolean qw(true);
use NGCP::Panel::Utils::DataHal qw();
use NGCP::Panel::Utils::DataHalLink qw();
use HTTP::Headers qw();
use Sipwise::Base;
use NGCP::Panel::Utils::Generic qw(:all);
use HTTP::Status qw(:constants);
use NGCP::Panel::Utils::DateTime;
use NGCP::Panel::Utils::Rewrite;
use Path::Tiny qw(path);
use Safe::Isa qw($_isa);
require Catalyst::ActionRole::ACL;
require Catalyst::ActionRole::CheckTrailingSlash;
require NGCP::Panel::Role::HTTPMethods;
require Catalyst::ActionRole::RequireSSL;
__PACKAGE__->set_config();
sub _set_config{
my ($self, $method) = @_;
return {
own_transaction_control => { POST => 1 },
};
}
sub allowed_methods{
return [qw/GET POST OPTIONS HEAD/];
@ -31,240 +30,42 @@ sub query_params {
{
param => 'reseller_id',
description => 'Filter for rewriterulesets belonging to a specific reseller',
query => {
first => sub {
my $q = shift;
{ reseller_id => $q };
},
second => sub {},
},
query_type => 'string_eq',
},
{
param => 'description',
description => 'Filter rulesets for a certain description (wildcards possible).',
query => {
first => sub {
my $q = shift;
return { description => { like => $q } };
},
second => sub {},
},
},
query_type => 'string_like',
},
{
param => 'name',
description => 'Filter rulesets for a certain name (wildcards possible).',
query => {
first => sub {
my $q = shift;
return { name => { like => $q } };
},
second => sub {},
},
query_type => 'string_like',
},
];
}
use parent qw/Catalyst::Controller NGCP::Panel::Role::API::RewriteRuleSets/;
sub resource_name{
return 'rewriterulesets';
}
sub dispatch_path{
return '/api/rewriterulesets/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-rewriterulesets';
}
__PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => [qw/admin reseller/],
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,
Path => __PACKAGE__->dispatch_path,
} } @{ __PACKAGE__->allowed_methods },
},
);
sub gather_default_action_roles {
my ($self, %args) = @_; my @roles = ();
push @roles, 'NGCP::Panel::Role::HTTPMethods' if $args{attributes}->{Method};
return @roles;
}
sub auto :Private {
my ($self, $c) = @_;
$self->set_body($c);
$self->log_request($c);
return 1;
}
sub GET :Allow {
my ($self, $c) = @_;
my $page = $c->request->params->{page} // 1;
my $rows = $c->request->params->{rows} // 10;
{
my $rwr_set = $self->item_rs($c, "rulesets");
(my $total_count, $rwr_set) = $self->paginate_order_collection($c, $rwr_set);
my (@embedded, @links);
for my $set ($rwr_set->all) {
push @embedded, $self->hal_from_item($c, $set, "rewriterulesets");
push @links, NGCP::Panel::Utils::DataHalLink->new(
relation => 'ngcp:'.$self->resource_name,
href => sprintf('%s%d', $self->dispatch_path, $set->id),
);
}
push @links,
NGCP::Panel::Utils::DataHalLink->new(
relation => 'curies',
href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}',
name => 'ngcp',
templated => true,
),
NGCP::Panel::Utils::DataHalLink->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'),
NGCP::Panel::Utils::DataHalLink->new(relation => 'self', href => sprintf('%s?page=%s&rows=%s', $self->dispatch_path, $page, $rows));
if(($total_count / $rows) > $page ) {
push @links, NGCP::Panel::Utils::DataHalLink->new(relation => 'next', href => sprintf('%s?page=%d&rows=%d', $self->dispatch_path, $page + 1, $rows));
}
if($page > 1) {
push @links, NGCP::Panel::Utils::DataHalLink->new(relation => 'prev', href => sprintf('%s?page=%d&rows=%d', $self->dispatch_path, $page - 1, $rows));
}
my $hal = NGCP::Panel::Utils::DataHal->new(
embedded => [@embedded],
links => [@links],
);
$hal->resource({
total_count => $total_count,
});
my $response = HTTP::Response->new(HTTP_OK, undef,
HTTP::Headers->new($hal->http_headers(skip_links => 1)), $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_filtered($c);
$c->response->headers(HTTP::Headers->new(
Allow => join(', ', @{ $allowed_methods }),
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 POST :Allow {
my ($self, $c) = @_;
my $guard = $c->model('DB')->txn_scope_guard;
sub create_item {
my ($self, $c, $resource, $form, $process_extras) = @_;
my $schema = $c->model('DB');
my $item;
my $guard = $schema->txn_scope_guard;
{
my $schema = $c->model('DB');
my $resource = $self->get_valid_post_data(
c => $c,
media_type => 'application/json',
);
last unless $resource;
my $reseller_id;
if($c->user->roles eq "admin") {
try {
$reseller_id = $resource->{reseller_id}
|| $c->user->contract->contact->reseller_id;
}
} elsif($c->user->roles eq "reseller") {
$reseller_id = $c->user->reseller_id;
}
$resource->{reseller_id} = $reseller_id;
my $reseller = $c->model('DB')->resultset('resellers')->find($resource->{reseller_id});
unless($reseller) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'reseller_id', doesn't exist.");
last;
}
my $rewriterules = $resource->{rewriterules};
my $form = $self->get_form($c);
last unless $self->validate_form(
c => $c,
resource => $resource,
form => $form,
);
my $ruleset_test = $schema->resultset('voip_rewrite_rule_sets')->search_rs({
name => $resource->{name}
})->first;
if ($ruleset_test) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Ruleset with this 'name' already exists.");
last;
}
my $ruleset;
my $rewriterules = delete $resource->{rewriterules};
try {
$ruleset = $schema->resultset('voip_rewrite_rule_sets')->create($resource);
$item = $schema->resultset('voip_rewrite_rule_sets')->create($resource);
} catch($e) {
$c->log->error("failed to create rewriteruleset: $e"); # TODO: user, message, trace, ...
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create rewriteruleset.");
last;
return;
}
if ($rewriterules) {
my $i = 30;
if (ref($rewriterules) ne "ARRAY") {
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "rewriterules must be an array.");
}
for my $rule (@{ $rewriterules }) {
my $rule_form = $self->get_form($c, "rules");
last unless $self->validate_form(
c => $c,
resource => $rule,
form => $rule_form,
);
try {
$ruleset->voip_rewrite_rules->create({
%{ $rule },
priority => $i++,
});
} catch($e) {
$c->log->error("failed to create rewriterules: $e"); # TODO: user, message, trace, ...
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create rewrite rules.");
last;
}
}
$self->update_rewriterules( $c, $item, $rewriterules );
}
$guard->commit;
NGCP::Panel::Utils::Rewrite::sip_dialplan_reload($c);
$c->response->status(HTTP_CREATED);
$c->response->header(Location => sprintf('/%s%d', $c->request->path, $ruleset->id));
$c->response->body(q());
}
return;
}
sub end : Private {
my ($self, $c) = @_;
$self->log_response($c);
return 1;
return $item;
}
1;

@ -1,223 +1,68 @@
package NGCP::Panel::Controller::API::RewriteRuleSetsItem;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use parent qw/NGCP::Panel::Role::EntitiesItem NGCP::Panel::Role::API::RewriteRuleSets/;
use boolean qw(true);
use NGCP::Panel::Utils::DataHal qw();
use NGCP::Panel::Utils::DataHalLink qw();
use HTTP::Headers qw();
use Sipwise::Base;
use NGCP::Panel::Utils::Generic qw(:all);
use HTTP::Status qw(:constants);
use NGCP::Panel::Utils::ValidateJSON qw();
use NGCP::Panel::Utils::DateTime;
use NGCP::Panel::Utils::Rewrite;
use Path::Tiny qw(path);
use Safe::Isa qw($_isa);
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/Catalyst::Controller NGCP::Panel::Role::API::RewriteRuleSets/;
__PACKAGE__->set_config();
sub resource_name{
return 'rewriterulesets';
}
sub dispatch_path{
return '/api/rewriterulesets/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-rewriterulesets';
sub _set_config{
my ($self, $method) = @_;
return {
own_transaction_control => { ALL => 1 },
};
}
__PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => [qw/admin reseller/],
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,
Path => __PACKAGE__->dispatch_path,
} } @{ __PACKAGE__->allowed_methods },
},
);
sub gather_default_action_roles {
my ($self, %args) = @_; my @roles = ();
push @roles, 'NGCP::Panel::Role::HTTPMethods' if $args{attributes}->{Method};
return @roles;
}
sub auto :Private {
my ($self, $c) = @_;
$self->set_body($c);
$self->log_request($c);
return 1;
}
sub GET :Allow {
my ($self, $c, $id) = @_;
{
last unless $self->valid_id($c, $id);
my $ruleset = $self->item_by_id($c, $id, "rulesets");
last unless $self->resource_exists($c, ruleset => $ruleset);
my $hal = $self->hal_from_item($c, $ruleset, "rewriterulesets");
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"|r
=~ s/rel=self/rel="item self"/r;
} $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_filtered($c);
$c->response->headers(HTTP::Headers->new(
Allow => join(', ', @{ $allowed_methods }),
Accept_Patch => 'application/json-patch+json',
));
$c->response->content_type('application/json');
$c->response->body(JSON::to_json({ methods => $allowed_methods })."\n");
return;
sub allowed_methods{
return [qw/GET OPTIONS HEAD PATCH PUT DELETE/];
}
sub PATCH :Allow {
my ($self, $c, $id) = @_;
sub delete_item {
my ($self, $c, $item) = @_;
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 $ruleset = $self->item_by_id($c, $id, "rulesets");
last unless $self->resource_exists($c, ruleset => $ruleset);
my $old_resource = $self->hal_from_item($c, $ruleset, "rewriterulesets")->resource;
my $resource = $self->apply_patch($c, $old_resource, $json);
last unless $resource;
my $form = $self->get_form($c);
$ruleset = $self->update_item($c, $ruleset, $old_resource, $resource, $form);
last unless $ruleset;
$guard->commit;
NGCP::Panel::Utils::Rewrite::sip_dialplan_reload($c);
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, $ruleset, "rewriterulesets");
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);
try {
$item->voip_rewrite_rules->delete;
$item->delete;
} catch($e) {
$c->log->error("Failed to delete rewriteruleset with id '".$item->id."': $e");
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error");
last;
}
$guard->commit;
NGCP::Panel::Utils::Rewrite::sip_dialplan_reload($c);
}
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 $ruleset = $self->item_by_id($c, $id, "rulesets");
last unless $self->resource_exists($c, ruleset => $ruleset);
my $resource = $self->get_valid_put_data(
c => $c,
id => $id,
media_type => 'application/json',
);
last unless $resource;
my $old_resource = { $ruleset->get_inflated_columns };
my $form = $self->get_form($c);
$ruleset = $self->update_item($c, $ruleset, $old_resource, $resource, $form);
last unless $ruleset;
$guard->commit;
NGCP::Panel::Utils::Rewrite::sip_dialplan_reload($c);
sub update_item_model {
my ($self, $c, $item, $old_resource, $resource, $form) = @_;
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, $ruleset, "rewriterulesets");
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;
}
my $id = delete $resource->{id};
sub DELETE :Allow {
my ($self, $c, $id) = @_;
my $guard = $c->model('DB')->txn_scope_guard;
{
my $ruleset = $self->item_by_id($c, $id, "rulesets");
last unless $self->resource_exists($c, ruleset => $ruleset);
try {
$ruleset->voip_rewrite_rules->delete;
$ruleset->delete;
my $rewriterules = delete $resource->{rewriterules};
$item->update($resource);
if ($rewriterules) {
$self->update_rewriterules( $c, $item, $rewriterules );
}
} catch($e) {
$c->log->error("Failed to delete rewriteruleset with id '$id': $e");
$c->log->error("Failed to update rewriterule with id '$id': $e");
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error");
last;
die;
}
$guard->commit;
NGCP::Panel::Utils::Rewrite::sip_dialplan_reload($c);
$c->response->status(HTTP_NO_CONTENT);
$c->response->body(q());
}
return;
}
sub end : Private {
my ($self, $c) = @_;
$self->log_response($c);
return 1;
return $item;
}
1;
# vim: set tabstop=4 expandtab:

@ -1,20 +1,20 @@
package NGCP::Panel::Controller::API::RewriteRules;
use NGCP::Panel::Utils::Generic qw(:all);
use parent qw/NGCP::Panel::Role::Entities NGCP::Panel::Role::API::RewriteRules/;
use Sipwise::Base;
use NGCP::Panel::Utils::Generic qw(:all);
use boolean qw(true);
use NGCP::Panel::Utils::DataHal qw();
use NGCP::Panel::Utils::DataHalLink qw();
use HTTP::Headers qw();
use HTTP::Status qw(:constants);
use NGCP::Panel::Utils::DateTime;
use NGCP::Panel::Utils::Rewrite;
require Catalyst::ActionRole::ACL;
require Catalyst::ActionRole::CheckTrailingSlash;
require NGCP::Panel::Role::HTTPMethods;
require Catalyst::ActionRole::RequireSSL;
__PACKAGE__->set_config();
sub _set_config{
my ($self, $method) = @_;
return {
own_transaction_control => { POST => 1 },
};
}
sub allowed_methods{
return [qw/GET POST OPTIONS HEAD/];
@ -29,24 +29,12 @@ sub query_params {
{
param => 'description',
description => 'Filter rules for a certain description (wildcards possible).',
query => {
first => sub {
my $q = shift;
return { description => { like => $q } };
},
second => sub {},
},
query_type => 'string_like',
},
{
param => 'set_id',
description => 'Filter for rules belonging to a specific rewriteruleset.',
query => {
first => sub {
my $q = shift;
return { set_id => $q };
},
second => sub {},
},
query_type => 'string_eq',
},
{
param => 'reseller_id',
@ -54,189 +42,33 @@ sub query_params {
query => {
first => sub {
my $q = shift;
return { set_id => $q };
return { 'ruleset.reseller_id' => $q };
},
second => sub {},
second => sub { join => 'ruleset' },
},
},
];
}
sub create_item {
my ($self, $c, $resource, $form, $process_extras) = @_;
my $schema = $c->model('DB');
my $guard = $schema->txn_scope_guard;
use parent qw/Catalyst::Controller NGCP::Panel::Role::API::RewriteRules/;
sub resource_name{
return 'rewriterules';
}
sub dispatch_path{
return '/api/rewriterules/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-rewriterules';
}
__PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => [qw/admin reseller/],
Args => 0,
Does => [qw(ACL CheckTrailingSlash RequireSSL)],
Method => $_,
Path => __PACKAGE__->dispatch_path,
} } @{ __PACKAGE__->allowed_methods },
},
);
sub gather_default_action_roles {
my ($self, %args) = @_; my @roles = ();
push @roles, 'NGCP::Panel::Role::HTTPMethods' if $args{attributes}->{Method};
return @roles;
}
sub auto :Private {
my ($self, $c) = @_;
my $item;
$self->set_body($c);
$self->log_request($c);
return 1;
}
sub GET :Allow {
my ($self, $c) = @_;
my $page = $c->request->params->{page} // 1;
my $rows = $c->request->params->{rows} // 10;
{
my $rules = $self->item_rs($c, "rules");
(my $total_count, $rules) = $self->paginate_order_collection($c, $rules);
my (@embedded, @links);
for my $rule ($rules->all) {
push @embedded, $self->hal_from_item($c, $rule, "rewriterules");
push @links, NGCP::Panel::Utils::DataHalLink->new(
relation => 'ngcp:'.$self->resource_name,
href => sprintf('%s%d', $self->dispatch_path, $rule->id),
);
}
push @links,
NGCP::Panel::Utils::DataHalLink->new(
relation => 'curies',
href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}',
name => 'ngcp',
templated => true,
),
NGCP::Panel::Utils::DataHalLink->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'),
NGCP::Panel::Utils::DataHalLink->new(relation => 'self', href => sprintf('%s?page=%s&rows=%s', $self->dispatch_path, $page, $rows));
if(($total_count / $rows) > $page ) {
push @links, NGCP::Panel::Utils::DataHalLink->new(relation => 'next', href => sprintf('%s?page=%d&rows=%d', $self->dispatch_path, $page + 1, $rows));
}
if($page > 1) {
push @links, NGCP::Panel::Utils::DataHalLink->new(relation => 'prev', href => sprintf('%s?page=%d&rows=%d', $self->dispatch_path, $page - 1, $rows));
}
my $hal = NGCP::Panel::Utils::DataHal->new(
embedded => [@embedded],
links => [@links],
);
$hal->resource({
total_count => $total_count,
});
my $response = HTTP::Response->new(HTTP_OK, undef,
HTTP::Headers->new($hal->http_headers(skip_links => 1)), $hal->as_json);
$c->response->headers($response->headers);
$c->response->body($response->content);
try {
$item = $schema->resultset('voip_rewrite_rules')->create($resource);
} catch($e) {
$c->log->error("failed to create rewriterule: $e"); # TODO: user, message, trace, ...
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create rewriterule.");
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_filtered($c);
$c->response->headers(HTTP::Headers->new(
Allow => join(', ', @{ $allowed_methods }),
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;
$guard->commit;
NGCP::Panel::Utils::Rewrite::sip_dialplan_reload($c);
return $item;
}
sub POST :Allow {
my ($self, $c) = @_;
my $guard = $c->model('DB')->txn_scope_guard;
{
my $schema = $c->model('DB');
my $resource = $self->get_valid_post_data(
c => $c,
media_type => 'application/json',
);
last unless $resource;
my $form = $self->get_form($c);
last unless $self->validate_form(
c => $c,
resource => $resource,
form => $form,
exceptions => [qw/set_id/],
);
$resource->{match_pattern} = $form->values->{match_pattern};
$resource->{replace_pattern} = $form->values->{replace_pattern};
my $rule;
unless(defined $resource->{set_id}) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Required: 'set_id'");
last;
}
my $reseller_id;
if($c->user->roles eq "reseller") {
$reseller_id = $c->user->reseller_id;
}
my $ruleset = $schema->resultset('voip_rewrite_rule_sets')->find({
id => $resource->{set_id},
($reseller_id ? (reseller_id => $reseller_id) : ()),
});
unless($ruleset) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'set_id'.");
last;
}
try {
$rule = $schema->resultset('voip_rewrite_rules')->create($resource);
} catch($e) {
$c->log->error("failed to create rewriterule: $e"); # TODO: user, message, trace, ...
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create rewriterule.");
last;
}
$guard->commit;
NGCP::Panel::Utils::Rewrite::sip_dialplan_reload($c);
$c->response->status(HTTP_CREATED);
$c->response->header(Location => sprintf('/%s%d', $c->request->path, $rule->id));
$c->response->body(q());
}
return;
}
sub end : Private {
my ($self, $c) = @_;
$self->log_response($c);
return 1;
}
1;
# vim: set tabstop=4 expandtab:

@ -1,222 +1,60 @@
package NGCP::Panel::Controller::API::RewriteRulesItem;
use NGCP::Panel::Utils::Generic qw(:all);
use Sipwise::Base;
use parent qw/NGCP::Panel::Role::EntitiesItem NGCP::Panel::Role::API::RewriteRules/;
use boolean qw(true);
use NGCP::Panel::Utils::DataHal qw();
use NGCP::Panel::Utils::DataHalLink qw();
use HTTP::Headers qw();
use Sipwise::Base;
use NGCP::Panel::Utils::Generic qw(:all);
use HTTP::Status qw(:constants);
use NGCP::Panel::Utils::ValidateJSON qw();
use NGCP::Panel::Utils::DateTime;
use NGCP::Panel::Utils::Rewrite;
use Path::Tiny qw(path);
use Safe::Isa qw($_isa);
require Catalyst::ActionRole::ACL;
require NGCP::Panel::Role::HTTPMethods;
require Catalyst::ActionRole::RequireSSL;
__PACKAGE__->set_config();
sub allowed_methods{
return [qw/GET OPTIONS HEAD PATCH PUT DELETE/];
sub _set_config{
my ($self, $method) = @_;
return {
own_transaction_control => { ALL => 1 },
};
}
use parent qw/Catalyst::Controller NGCP::Panel::Role::API::RewriteRules/;
sub resource_name{
return 'rewriterules';
}
sub dispatch_path{
return '/api/rewriterules/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-rewriterules';
}
__PACKAGE__->config(
action => {
map { $_ => {
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => [qw/admin reseller/],
Args => 1,
Does => [qw(ACL RequireSSL)],
Method => $_,
Path => __PACKAGE__->dispatch_path,
} } @{ __PACKAGE__->allowed_methods },
},
);
sub gather_default_action_roles {
my ($self, %args) = @_; my @roles = ();
push @roles, 'NGCP::Panel::Role::HTTPMethods' if $args{attributes}->{Method};
return @roles;
}
sub auto :Private {
my ($self, $c) = @_;
$self->set_body($c);
$self->log_request($c);
return 1;
}
sub GET :Allow {
my ($self, $c, $id) = @_;
{
last unless $self->valid_id($c, $id);
my $rule = $self->item_by_id($c, $id, "rules");
last unless $self->resource_exists($c, rule => $rule);
my $hal = $self->hal_from_item($c, $rule, "rewriterules");
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"|r
=~ s/rel=self/rel="item self"/r;
} $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_filtered($c);
$c->response->headers(HTTP::Headers->new(
Allow => join(', ', @{ $allowed_methods }),
Accept_Patch => 'application/json-patch+json',
));
$c->response->content_type('application/json');
$c->response->body(JSON::to_json({ methods => $allowed_methods })."\n");
return;
sub allowed_methods{
return [qw/GET OPTIONS HEAD PATCH PUT DELETE/];
}
sub PATCH :Allow {
my ($self, $c, $id) = @_;
sub delete_item {
my ($self, $c, $item) = @_;
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 $rule = $self->item_by_id($c, $id, "rules");
last unless $self->resource_exists($c, rule => $rule);
my $old_resource = { $rule->get_inflated_columns };
my $resource = $self->apply_patch($c, $old_resource, $json);
last unless $resource;
my $form = $self->get_form($c);
$rule = $self->update_item($c, $rule, $old_resource, $resource, $form);
last unless $rule;
$guard->commit;
NGCP::Panel::Utils::Rewrite::sip_dialplan_reload($c);
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, $rule, "rewriterules");
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);
try {
$item->delete;
} catch($e) {
$c->log->error("Failed to delete rewriterule with id '".$item->id."': $e");
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error");
die;
}
}
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 $rule = $self->item_by_id($c, $id, "rules");
last unless $self->resource_exists($c, rule => $rule);
my $resource = $self->get_valid_put_data(
c => $c,
id => $id,
media_type => 'application/json',
);
last unless $resource;
my $old_resource = { $rule->get_inflated_columns };
my $form = $self->get_form($c);
$rule = $self->update_item($c, $rule, $old_resource, $resource, $form);
last unless $rule;
$guard->commit;
NGCP::Panel::Utils::Rewrite::sip_dialplan_reload($c);
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, $rule, "rewriterules");
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 DELETE :Allow {
my ($self, $c, $id) = @_;
sub update_item_model {
my($self, $c, $item, $old_resource, $resource, $form, $process_extras) = @_;
my $id = delete $resource->{id};
my $guard = $c->model('DB')->txn_scope_guard;
{
my $rule = $self->item_by_id($c, $id, "rules");
last unless $self->resource_exists($c, rule => $rule);
try {
$rule->delete;
$item->update($resource);
} catch($e) {
$c->log->error("Failed to delete rewriterule with id '$id': $e");
$c->log->error("Failed to update rewriterule with id '$id': $e");
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error");
last;
die;
}
$guard->commit;
NGCP::Panel::Utils::Rewrite::sip_dialplan_reload($c);
$c->response->status(HTTP_NO_CONTENT);
$c->response->body(q());
}
return;
return $item;
}
sub end : Private {
my ($self, $c) = @_;
$self->log_response($c);
return 1;
}
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,105 @@
package NGCP::Panel::Field::RewriteRule;
use HTML::FormHandler::Moose;
extends 'HTML::FormHandler::Field::Compound';
has_field 'match_pattern' => (
type => '+NGCP::Panel::Field::Regexp',
required => 1,
inflate_default_method => \&inflate_match_pattern,
deflate_value_method => \&deflate_match_pattern,
element_attr => {
rel => ['tooltip'],
title => ['Match pattern, a regular expression.'],
},
);
has_field 'replace_pattern' => (
type => 'Text',
required => 1,
label => 'Replacement Pattern',
inflate_default_method => \&inflate_replace_pattern,
deflate_value_method => \&deflate_replace_pattern,
element_attr => {
rel => ['tooltip'],
title => ['Replacement pattern.'],
},
);
has_field 'description' => (
type => 'Text',
required => 1,
element_attr => {
rel => ['tooltip'],
title => ['Arbitrary text.'],
},
);
has_field 'direction' => (
type => 'Select',
options => [
{ label => 'Inbound', value => 'in'},
{ label => 'Outbound', value => 'out'},
{ label => 'LNP', value => 'lnp'},
],
element_attr => {
rel => ['tooltip'],
title => ['Inbound (in), Outbound (out) or LNP (lnp).']
},
);
has_field 'enabled' => (
type => 'Boolean',
label => 'Enabled',
default => 1,
element_attr => {
rel => ['tooltip'],
title => ['Rule enabled state.'],
},
);
has_field 'field' => (
type => 'Select',
options => [
{ label => 'Callee', value => 'callee'},
{ label => 'Caller', value => 'caller'},
],
element_attr => {
rel => ['tooltip'],
title => ['caller or callee.']
},
);
sub deflate_match_pattern {
my ($self, $value) = @_;
$value =~ s/\$\{(\w+)\}/\$avp(s:$1)/g;
$value =~ s/\@\{(\w+)\}/\$(avp(s:$1)[+])/g;
return $value;
};
sub inflate_match_pattern {
my ($self, $value) = @_;
$value =~ s/\$avp\(s\:(\w+)\)/\${$1}/g;
$value =~ s/\$\(avp\(s\:(\w+)\)\[\+\]\)/\@{$1}/g;
return $value;
}
sub deflate_replace_pattern {
my ($self, $value) = @_;
$value =~ s/\$\{(\w+)\}/\$avp(s:$1)/g;
return $value;
};
sub inflate_replace_pattern {
my ($self, $value) = @_;
$value =~ s/\$avp\(s\:(\w+)\)/\${$1}/g;
return $value;
}
no Moose;
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,18 @@
package NGCP::Panel::Field::RewriteRuleAPI;
use Moose;
use HTML::FormHandler::Moose;
extends 'NGCP::Panel::Field::RewriteRule';
has_field 'priority' => (
type => 'PosInteger',
required => 0,
element_attr => {
rel => ['tooltip'],
title => ['The rewrite rule priority.']
},
);
no Moose;
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,23 @@
package NGCP::Panel::Form::RewriteRule::AdminSetAPI;
use HTML::FormHandler::Moose;
extends 'NGCP::Panel::Form::RewriteRule::ResellerSetAPI';
has_field 'reseller_id' => (
type => 'PosInteger',
validate_when_empty => 1,
element_attr => {
rel => ['tooltip'],
title => ['The reseller who can use the Ruleset.'],
},
);
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/reseller_id name description rewriterules/],
);
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,29 @@
package NGCP::Panel::Form::RewriteRule::ResellerSetAPI;
use HTML::FormHandler::Moose;
extends 'NGCP::Panel::Form::RewriteRule::ResellerSet';
has_field 'rewriterules' => (
type => 'Repeatable',
required => 0,
setup_for_js => 1,
do_wrapper => 1,
do_label => 0,
wrapper_class => [qw/hfh-rep/],
element_attr => {
rel => ['tooltip'],
title => ['Rewrite Rules'],
},
);
has_field 'rewriterules.contains' => ( type => '+NGCP::Panel::Field::RewriteRuleAPI' );
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/name description rewriterules/],
);
1;
# vim: set tabstop=4 expandtab:

@ -12,10 +12,19 @@ has_field 'set_id' => (
},
);
has_field 'priority' => (
type => 'PosInteger',
required => 0,
element_attr => {
rel => ['tooltip'],
title => ['The rewrite rule priority.']
},
);
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/set_id match_pattern replace_pattern description direction enabled field/],
render_list => [qw/set_id match_pattern replace_pattern description direction enabled priority field/],
);
1;

@ -849,7 +849,6 @@ sub hal_from_item {
$form_exceptions = $params->{form_exceptions};
}
my $resource = $self->resource_from_item($c, $item, $form);
$resource = $self->process_hal_resource($c, $item, $resource, $form);
my $links = $self->hal_links($c, $item, $resource, $form) // [];
my $hal = NGCP::Panel::Utils::DataHal->new(
@ -878,6 +877,7 @@ sub hal_from_item {
);
}
$resource->{id} = $self->get_item_id($c, $item);
$resource = $self->post_process_hal_resource($c, $item, $resource, $form);
$hal->resource({%$resource});
return $hal;
}
@ -888,10 +888,13 @@ sub update_item {
($form, $form_exceptions, $process_extras) = @{$params}{qw/form form_exceptions process_extras/};
if(!$form){
($form, $form_exceptions) = $self->get_form($c);
($form, $form_exceptions) = $self->get_form($c, 'edit');
}
if($form){
if(!$form_exceptions && $form->can('form_exceptions')){
$form_exceptions = $form->form_exceptions;
}
return unless $self->validate_form(
c => $c,
resource => $resource,
@ -964,6 +967,11 @@ sub process_hal_resource {
return $resource;
}
sub post_process_hal_resource {
my($self, $c, $item, $resource, $form) = @_;
return $resource;
}
sub hal_links {
my($self, $c, $item, $resource, $form) = @_;
return [];
@ -1002,7 +1010,55 @@ sub update_item_model{
return $item;
}
sub post_process_commit{
my($self, $c, $action, $item, $old_resource, $resource, $form, $process_extras) = @_;
return;
}
sub check_transaction_control{
my($self, $c, $action, $step, %params) = @_;
my $res = 1;
my $config = $self->config->{own_transaction_control};
if(!$config){
$res = 1;
}else{
if($config->{ALL}){
$res = 0;
}elsif( ('HASH' eq $self->config->{own_transaction_control}->{$action} && $self->config->{own_transaction_control}->{$action}->{$step} )
|| $self->config->{own_transaction_control}->{$action}){
$res = 0;
}
}
return $res;
}
sub get_transaction_control{
my $self = shift;
my($c, $action, $step, %params) = @_;
my $schema = $params{schema} // $c->model('DB');
$action //= uc $c->request->method;
$step //= 'init';
if($self->check_transaction_control($c, $action, $step, %params)){
#todo: put it into class variables?
$c->stash->{transaction_quard} = $schema->txn_scope_guard;
return $c->stash->{transaction_quard};
}
return;
}
sub complete_transaction{
my $self = shift;
my($c, $action, $step, %params) = @_;
my $schema = $params{schema} // $c->model('DB');
my $guard = $params{guard} // $c->stash->{transaction_quard};
$action //= uc $c->request->method;
$step //= 'commit';
if($self->check_transaction_control($c, $action, $step, %params)){
$guard->commit;
$c->stash->{transaction_quard} = undef;
}
return;
}
#------ accessors ---
sub dispatch_path {

@ -1,41 +1,48 @@
package NGCP::Panel::Role::API::RewriteRuleSets;
use NGCP::Panel::Utils::Generic qw(:all);
use parent qw/NGCP::Panel::Role::API/;
use Sipwise::Base;
use NGCP::Panel::Utils::Generic qw(:all);
use HTTP::Status qw(:constants);
use parent 'NGCP::Panel::Role::API';
use NGCP::Panel::Form::RewriteRule::AdminSetAPI;
use NGCP::Panel::Form::RewriteRule::ResellerSet;
sub item_name{
return 'rewriteruleset';
}
use boolean qw(true);
use NGCP::Panel::Utils::DataHal qw();
use NGCP::Panel::Utils::DataHalLink qw();
use HTTP::Status qw(:constants);
use JSON::Types;
use NGCP::Panel::Form::RewriteRule::AdminSet;
use NGCP::Panel::Form::RewriteRule::ResellerSet;
use NGCP::Panel::Form::RewriteRule::Rule;
sub resource_name{
return 'rewriterulesets';
}
sub dispatch_path{
return '/api/rewriterulesets/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-rewriterulesets';
}
sub get_form {
my ($self, $c, $type) = @_;
if ($type && $type eq "rules") {
return NGCP::Panel::Form::RewriteRule::Rule->new;
return (NGCP::Panel::Form::RewriteRule::RuleAPI->new);
}
if($c->user->roles eq "admin") {
return NGCP::Panel::Form::RewriteRule::AdminSet->new;
return (NGCP::Panel::Form::RewriteRule::AdminSetAPI->new( ctx => $c), [qw/reseller_id/]);
} else {
return NGCP::Panel::Form::RewriteRule::ResellerSet->new;
return (NGCP::Panel::Form::RewriteRule::ResellerSet->new( ctx => $c), [qw/reseller_id/]);
}
}
sub hal_from_item {
my ($self, $c, $item, $type) = @_;
my $form;
sub post_process_hal_resource {
my($self, $c, $item, $resource, $form) = @_;
my $rwr_form = $self->get_form($c, "rules");
my %resource = $item->get_inflated_columns;
my @rewriterules;
for my $rule ( $item->voip_rewrite_rules->all ) {
for my $rule ( $item->voip_rewrite_rules->search_rs(undef, { order_by => { '-asc' => 'priority' } } )->all ) {
my $rule_resource = { $rule->get_inflated_columns };
return unless $self->validate_form(
c => $c,
@ -43,71 +50,45 @@ sub hal_from_item {
resource => $rule_resource,
run => 0,
);
delete $rule_resource->{set_id};
push @rewriterules, $rule_resource;
}
my $hal = NGCP::Panel::Utils::DataHal->new(
links => [
NGCP::Panel::Utils::DataHalLink->new(
relation => 'curies',
href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}',
name => 'ngcp',
templated => true,
),
NGCP::Panel::Utils::DataHalLink->new(relation => 'collection', href => sprintf("%s", $self->dispatch_path)),
NGCP::Panel::Utils::DataHalLink->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'),
NGCP::Panel::Utils::DataHalLink->new(relation => 'self', href => sprintf("%s%d", $self->dispatch_path, $item->id)),
NGCP::Panel::Utils::DataHalLink->new(relation => "ngcp:$type", href => sprintf("/api/%s/%d", $type, $item->id)),
],
relation => 'ngcp:'.$self->resource_name,
);
$form //= $self->get_form($c);
return unless $self->validate_form(
c => $c,
form => $form,
resource => \%resource,
run => 0,
);
$resource{rewriterules} = \@rewriterules;
$hal->resource(\%resource);
return $hal;
$resource->{rewriterules} = \@rewriterules;
return $resource;
}
sub _item_rs {
my ($self, $c, $type) = @_;
my $item_rs;
if($type eq "rulesets") {
if($c->user->roles eq "admin") {
$item_rs = $c->model('DB')->resultset('voip_rewrite_rule_sets');
} elsif($c->user->roles eq "reseller") {
$item_rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')
->search_rs({reseller_id => $c->user->reseller_id});
}
} else {
die "You should not reach this";
if($c->user->roles eq "admin") {
$item_rs = $c->model('DB')->resultset('voip_rewrite_rule_sets');
} elsif($c->user->roles eq "reseller") {
$item_rs = $c->model('DB')->resultset('voip_rewrite_rule_sets')
->search_rs({reseller_id => $c->user->reseller_id});
}
return $item_rs;
}
sub item_by_id {
my ($self, $c, $id, $type) = @_;
my $item_rs = $self->item_rs($c, $type);
return $item_rs->find($id);
}
sub update_item {
my ($self, $c, $item, $old_resource, $resource, $form) = @_;
delete $resource->{id};
if($c->user->roles eq "reseller") {
$resource->{reseller_id} = $old_resource->{reseller_id}; # prohibit change
sub process_form_resource{
my($self,$c, $item, $old_resource, $resource, $form, $process_extras) = @_;
my $reseller_id;
if($c->user->roles eq "admin") {
try {
$reseller_id = $resource->{reseller_id}
|| $c->user->contract->contact->reseller_id;
}
} elsif($c->user->roles eq "reseller") {
$reseller_id = $c->user->reseller_id;
}
$resource->{reseller_id} = $reseller_id;
return $resource;
}
if($old_resource->{reseller_id} != $resource->{reseller_id}) {
sub check_resource{
my($self, $c, $item, $old_resource, $resource, $form, $process_extras) = @_;
my $schema = $c->model('DB');
if(!$old_resource || ( $old_resource->{reseller_id} != $resource->{reseller_id}) ) {
my $reseller = $c->model('DB')->resultset('resellers')
->find($resource->{reseller_id});
unless($reseller) {
@ -115,28 +96,47 @@ sub update_item {
return;
}
}
return 1;
}
sub check_duplicate{
my($self, $c, $item, $old_resource, $resource, $form, $process_extras) = @_;
if ($resource->{rewriterules}) {
$item->voip_rewrite_rules->delete;
my $i = 30;
for my $rule (@{ $resource->{rewriterules} }) {
my $schema = $c->model('DB');
my $existing_item = $schema->resultset('voip_rewrite_rule_sets')->search_rs({
name => $resource->{name}
})->first;
if ($existing_item && (!$item || $item->id != $existing_item->id)) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Ruleset with this 'name' already exists.");
return;
}
return 1;
}
sub update_rewriterules{
my($self, $c, $item, $rewriterules ) = @_;
my $schema = $c->model('DB');
my $priority = 30;
if (ref($rewriterules) ne "ARRAY") {
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "rewriterules must be an array.");
die;
}
$item->voip_rewrite_rules->delete;
for my $rule (@{ $rewriterules }) {
try {
$item->voip_rewrite_rules->create({
priority => $priority++,
%{ $rule },
priority => $i++,
});
} catch($e) {
$c->log->error("failed to create rewriterules: $e"); # TODO: user, message, trace, ...
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create rewrite rules.");
die;
}
}
return unless $self->validate_form(
c => $c,
form => $form,
resource => $resource,
);
#TODO: priority not accessible here
$item->update($resource);
return $item;
}
1;
# vim: set tabstop=4 expandtab:

@ -1,110 +1,103 @@
package NGCP::Panel::Role::API::RewriteRules;
use NGCP::Panel::Utils::Generic qw(:all);
use parent qw/NGCP::Panel::Role::API/;
use Sipwise::Base;
use NGCP::Panel::Utils::Generic qw(:all);
use HTTP::Status qw(:constants);
use parent 'NGCP::Panel::Role::API';
use NGCP::Panel::Form::RewriteRule::RuleAPI;
sub item_name{
return 'rewriterule';
}
use boolean qw(true);
use NGCP::Panel::Utils::DataHal qw();
use NGCP::Panel::Utils::DataHalLink qw();
use HTTP::Status qw(:constants);
use JSON::Types;
use NGCP::Panel::Form::RewriteRule::AdminSet;
use NGCP::Panel::Form::RewriteRule::ResellerSet;
use NGCP::Panel::Form::RewriteRule::RuleAPI;
sub resource_name{
return 'rewriterules';
}
sub get_form {
my ($self, $c) = @_;
return NGCP::Panel::Form::RewriteRule::RuleAPI->new;
sub dispatch_path{
return '/api/rewriterules/';
}
sub hal_from_item {
my ($self, $c, $item, $type) = @_;
my $form;
my %resource = $item->get_inflated_columns;
my $hal = NGCP::Panel::Utils::DataHal->new(
links => [
NGCP::Panel::Utils::DataHalLink->new(
relation => 'curies',
href => 'http://purl.org/sipwise/ngcp-api/#rel-{rel}',
name => 'ngcp',
templated => true,
),
NGCP::Panel::Utils::DataHalLink->new(relation => 'collection', href => sprintf("%s", $self->dispatch_path)),
NGCP::Panel::Utils::DataHalLink->new(relation => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'),
NGCP::Panel::Utils::DataHalLink->new(relation => 'self', href => sprintf("%s%d", $self->dispatch_path, $item->id)),
NGCP::Panel::Utils::DataHalLink->new(relation => "ngcp:$type", href => sprintf("/api/%s/%d", $type, $item->id)),
NGCP::Panel::Utils::DataHalLink->new(relation => "ngcp:rewriterulesets", href => sprintf("/api/rewriterulesets/%d", $item->set_id)),
],
relation => 'ngcp:'.$self->resource_name,
);
$form //= $self->get_form($c);
return unless $self->validate_form(
c => $c,
form => $form,
resource => \%resource,
run => 0,
exceptions => [qw/set_id/],
);
$resource{match_pattern} = $form->inflate_match_pattern($resource{match_pattern});
$resource{replace_pattern} = $form->inflate_replace_pattern($resource{replace_pattern});
$hal->resource(\%resource);
return $hal;
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-rewriterules';
}
sub config_allowed_roles {
return [qw/admin reseller/];
}
sub _item_rs {
my ($self, $c, $type) = @_;
my $item_rs;
if($type eq "rules") {
if($c->user->roles eq "admin") {
$item_rs = $c->model('DB')->resultset('voip_rewrite_rules');
} elsif ($c->user->roles eq "reseller") {
$item_rs = $c->model('DB')->resultset('voip_rewrite_rules')->search_rs({
'ruleset.reseller_id' => $c->user->reseller_id,
},{
join => 'ruleset'
});
}
} else {
die "You should not reach this";
if($c->user->roles eq "admin") {
$item_rs = $c->model('DB')->resultset('voip_rewrite_rules');
} elsif ($c->user->roles eq "reseller") {
$item_rs = $c->model('DB')->resultset('voip_rewrite_rules')->search_rs({
'ruleset.reseller_id' => $c->user->reseller_id,
},{
join => 'ruleset'
});
}
return $item_rs;
}
sub item_by_id {
my ($self, $c, $id, $type) = @_;
my $item_rs = $self->item_rs($c, $type);
return $item_rs->find($id);
sub get_form {
my ($self, $c) = @_;
return ( NGCP::Panel::Form::RewriteRule::RuleAPI->new, ['set_id'] );
}
sub update_item {
my ($self, $c, $item, $old_resource, $resource, $form) = @_;
delete $resource->{id};
sub hal_links{
my($self, $c, $item, $resource, $form) = @_;
return [
NGCP::Panel::Utils::DataHalLink->new(relation => "ngcp:rewriterulesets", href => sprintf("/api/rewriterulesets/%d", $item->set_id)),
];
}
return unless $self->validate_form(
c => $c,
form => $form,
resource => $resource,
exceptions => [qw/set_id/],
);
sub post_process_hal_resource {
my($self, $c, $item, $resource, $form) = @_;
$resource->{match_pattern} = $form->inflate_match_pattern($resource->{match_pattern});
$resource->{replace_pattern} = $form->inflate_replace_pattern($resource->{replace_pattern});
return $resource;
}
sub process_form_resource{
my($self,$c, $item, $old_resource, $resource, $form, $process_extras) = @_;
$resource->{match_pattern} = $form->values->{match_pattern};
$resource->{replace_pattern} = $form->values->{replace_pattern};
return $resource;
}
sub check_resource{
my($self, $c, $item, $old_resource, $resource, $form, $process_extras) = @_;
my $schema = $c->model('DB');
$item->update($resource);
unless(defined $resource->{set_id}) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Required: 'set_id'");
return;
}
my $reseller_id;
if($c->user->roles eq "reseller") {
$reseller_id = $c->user->reseller_id;
}
return $item;
my $ruleset = $schema->resultset('voip_rewrite_rule_sets')->find({
id => $resource->{set_id},
($reseller_id ? (reseller_id => $reseller_id) : ()),
});
unless($ruleset) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'set_id'.");
return;
}
$c->stash->{checked}->{ruleset} = $ruleset;
return 1;
}
1;
# vim: set tabstop=4 expandtab:

@ -30,6 +30,8 @@ sub set_config {
},
#action_roles => [qw(HTTPMethods)],
%{$self->_set_config()},
#log_response = 0|1 - don't log response body
#own_transaction_control->{PUT|POST|PATCH|DELETE} = 0|1 - don't start transaction guard
);
}
@ -105,7 +107,7 @@ sub get {
sub post {
my ($self) = shift;
my ($c) = @_;
my $guard = $c->model('DB')->txn_scope_guard;
my $guard = $self->get_transaction_control($c);
{
my $method_config = $self->config->{action}->{POST};
my ($resource) = $self->get_valid_data(
@ -115,7 +117,11 @@ sub post {
uploads => $method_config->{Uploads} // [] ,
);
last unless $resource;
my ($form, $form_exceptions) = $self->get_form($c);
#instead of type parameter get_form can check request method
my ($form, $form_exceptions) = $self->get_form($c, 'add');
if(!$form_exceptions && $form->can('form_exceptions')){
$form_exceptions = $form->form_exceptions;
}
last unless $self->validate_form(
c => $c,
resource => $resource,
@ -132,8 +138,10 @@ sub post {
my $item = $self->create_item($c, $resource, $form, $process_extras);
last unless $item;
$guard->commit;
$self->complete_transaction($c);
$self->post_process_commit($c, 'create', $item, undef, $resource, $form, $process_extras);
$self->return_representation_post($c, 'item' => $item, 'form' => $form, 'form_exceptions' => $form_exceptions );
}
return;

@ -35,6 +35,8 @@ sub set_config {
#action_roles => [qw(HTTPMethods)],
log_response => 1,
%{$self->_set_config()},
#log_response = 0|1 - don't log response body
#own_transaction_control = {post|put|patch|delete|all => 1|0}
);
}
@ -54,7 +56,10 @@ 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);
return;
}
@ -76,7 +81,7 @@ sub get {
sub patch {
my ($self, $c, $id) = @_;
my $guard = $c->model('DB')->txn_scope_guard;
my $guard = $self->get_transaction_control($c);
{
my $preference = $self->require_preference($c);
last unless $preference;
@ -94,11 +99,14 @@ sub patch {
my $resource = $self->apply_patch($c, $old_resource, $json);
last unless $resource;
my $form;
($item,$form) = $self->update_item($c, $item, $old_resource, $resource, $form);
my ($form, $form_exceptions, $process_extras);
($item, $form, $form_exceptions, $process_extras) = $self->update_item($c, $item, $old_resource, $resource, $form, $process_extras );
last unless $item;
$guard->commit;
$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 );
}
return;
@ -106,7 +114,7 @@ sub patch {
sub put {
my ($self, $c, $id) = @_;
my $guard = $c->model('DB')->txn_scope_guard;
my $guard = $self->get_transaction_control($c);
{
my $preference = $self->require_preference($c);
last unless $preference;
@ -124,12 +132,13 @@ sub put {
last unless $resource;
my $old_resource = { $item->get_inflated_columns };
#TODO: MOVE form exceptions to proper forms as property
my ($form, $form_exceptions);
my ($form, $form_exceptions, $process_extras);
($item, $form, $form_exceptions) = $self->update_item($c, $item, $old_resource, $resource, $form );
($item, $form, $form_exceptions, $process_extras) = $self->update_item($c, $item, $old_resource, $resource, $form, $process_extras );
last unless $item;
$guard->commit;
$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 );
}
@ -140,13 +149,15 @@ sub put {
sub delete {
my ($self, $c, $id) = @_;
my $guard = $c->model('DB')->txn_scope_guard;
my $guard = $self->get_transaction_control($c);
{
my $item = $self->item_by_id_valid($c, $id);
last unless $item;
$self->delete_item($c, $item );
$guard->commit;
$self->complete_transaction($c);
$self->post_process_commit($c, 'delete', $item);
$c->response->status(HTTP_NO_CONTENT);
$c->response->body(q());

@ -41,5 +41,94 @@ $test_machine->check_bundle();
$test_machine->clear_test_data_all();
done_testing;
__DATA__
# try to create rule with invalid set_id
$req = HTTP::Request->new('POST', $uri.'/api/rewriterules/');
$req->header('Content-Type' => 'application/json');
$req->content(JSON::to_json({
set_id => 999999,
description => "test rule $t",
direction => "in",
field => "caller",
match_pattern => "test pattern $t",
replace_pattern => "test_replace_$t",
}));
$res = $ua->request($req);
is($res->code, 422, "create rule with invalid set_id");
$err = JSON::from_json($res->decoded_content);
is($err->{code}, "422", "check error code in body");
like($err->{message}, qr/Invalid 'set_id'/, "check error message in body");
# try to create rule with negative set_id
$req = HTTP::Request->new('POST', $uri.'/api/rewriterules/');
$req->header('Content-Type' => 'application/json');
$req->content(JSON::to_json({
set_id => -100,
description => "test rule $t",
direction => "in",
field => "caller",
match_pattern => "test pattern $t",
replace_pattern => "test_replace_$t",
}));
$res = $ua->request($req);
is($res->code, 422, "create rule with negative set_id");
$err = JSON::from_json($res->decoded_content);
is($err->{code}, "422", "check error code in body");
like($err->{message}, qr/(Invalid|Validation failed).*'set_id'/, "check error message in body");
# try to create rule with missing match_pattern
$req = HTTP::Request->new('POST', $uri.'/api/rewriterules/');
$req->header('Content-Type' => 'application/json');
$req->content(JSON::to_json({
set_id => $rewriteruleset_id,
description => "test rule $t",
direction => "in",
field => "caller",
#match_pattern => "test pattern $t",
replace_pattern => "test_replace_$t",
}));
$res = $ua->request($req);
is($res->code, 422, "create rule with missing match_pattern");
$err = JSON::from_json($res->decoded_content);
is($err->{code}, "422", "check error code in body");
like($err->{message}, qr/field='match_pattern'/, "check error message in body");
# try to create rule with invalid direction and field
$req = HTTP::Request->new('POST', $uri.'/api/rewriterules/');
$req->header('Content-Type' => 'application/json');
$req->content(JSON::to_json({
set_id => $rewriteruleset_id,
description => "test rule $t",
direction => "foo",
field => "bar",
match_pattern => "test pattern $t",
replace_pattern => "test_replace_$t",
}));
$res = $ua->request($req);
is($res->code, 422, "create rule with invalid direction and field");
$err = JSON::from_json($res->decoded_content);
is($err->{code}, "422", "check error code in body");
like($err->{message}, qr/field='direction'/, "check error message in body");
like($err->{message}, qr/field='field'/, "check error message in body");
# try to create rule without set_id
$req = HTTP::Request->new('POST', $uri.'/api/rewriterules/');
$req->header('Content-Type' => 'application/json');
$req->content(JSON::to_json({
#set_id => $rewriteruleset_id,
description => "test rule $t",
direction => "in",
field => "caller",
match_pattern => "test pattern $t",
replace_pattern => "test_replace_$t",
}));
$res = $ua->request($req);
is($res->code, 422, "create rule without set_id");
$err = JSON::from_json($res->decoded_content);
is($err->{code}, "422", "check error code in body");
like($err->{message}, qr/Required: 'set_id'|set_id.*required/, "check error message in body");
# vim: set tabstop=4 expandtab:

@ -1,3 +1,102 @@
use strict;
use warnings;
use Test::Collection;
use Test::FakeData;
use Test::More;
use Data::Dumper;
#init test_machine
my $test_machine = Test::Collection->new(
name => 'rewriterulesets',
embedded_resources => []
);
$test_machine->methods->{collection}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS POST)};
$test_machine->methods->{item}->{allowed} = {map {$_ => 1} qw(GET HEAD OPTIONS PUT PATCH DELETE)};
my $fake_data = Test::FakeData->new;
$fake_data->set_data_from_script({
rewriterulesets => {
'data' => {
reseller_id => sub { return shift->get_id('resellers',@_); },
name => 'api_test',
description => 'api_test rule set description',
caller_in_dpid => '1',
callee_in_dpid => '2',
caller_out_dpid => '3',
callee_out_dpid => '4',
rewriterules => [{
match_pattern => '^1111$',
replace_pattern => '2221',
description => 'test_api rewrite rule 1',
direction => 'in',#out
field => 'caller',#calee
priority => '2',
enabled => '1',
},{
match_pattern => '^1112$',
replace_pattern => '2222',
description => 'test_api rewrite rule 2',
direction => 'in',#out
field => 'caller',#calee
priority => '3',
enabled => '1',
},{
match_pattern => '^1113$',
replace_pattern => '2223',
description => 'test_api rewrite rule 3',
direction => 'in',#out
field => 'caller',#calee
priority => '1',
enabled => '1',
},
],
},
'query' => ['name'],
'uniquizer_cb' => sub { Test::FakeData::string_uniquizer(\$_[0]->{name}); },
},
});
#for item creation test purposes /post request data/
$test_machine->DATA_ITEM_STORE($fake_data->process('rewriterulesets'));
$test_machine->form_data_item( );
# create 3 new field pbx devices from DATA_ITEM
my $sets = $test_machine->check_create_correct( 3, sub{ $_[0]->{name} .= $_[1]->{i}.time(); } );
$test_machine->check_get2put();
$test_machine->check_bundle();
print Dumper $sets;
for(my $i=0; $i < scalar @$sets; $i++){
my $set = $sets->[$i];
my $rewriterules = $sets->[$i]->{content}->{rewriterules};
my $priority = -1;
for(my $j=0; $j < scalar @{$rewriterules}; $j++){
my $priority_new = $rewriterules->[$j]->{priority};
diag("Check priority order $i:$j: $priority < $priority_new");
ok($priority < $priority_new) ;
}
}
# try to create model without reseller_id
{
my ($res, $err) = $test_machine->check_item_post(sub{delete $_[0]->{reseller_id};});
is($res->code, 422, "create model without reseller_id");
is($err->{code}, "422", "check error code in body");
ok($err->{message} =~ /field='reseller_id'/, "check error message in body");
}
$test_machine->clear_test_data_all();
done_testing;
__DATA__
use warnings;
use strict;
@ -13,24 +112,7 @@ my ($ua, $req, $res);
use Test::Collection;
$ua = Test::Collection->new()->ua();
# OPTIONS tests
{
$req = HTTP::Request->new('OPTIONS', $uri.'/api/rewriterulesets/');
$res = $ua->request($req);
is($res->code, 200, "check options request");
is($res->header('Accept-Post'), "application/hal+json; profile=http://purl.org/sipwise/ngcp-api/#rel-rewriterulesets", "check Accept-Post header in options response");
my $opts = JSON::from_json($res->decoded_content);
my @hopts = split /\s*,\s*/, $res->header('Allow');
ok(exists $opts->{methods} && ref $opts->{methods} eq "ARRAY", "check for valid 'methods' in body");
foreach my $opt(qw( GET HEAD OPTIONS POST )) {
ok(grep(/^$opt$/, @hopts), "check for existence of '$opt' in Allow header");
ok(grep(/^$opt$/, @{ $opts->{methods} }), "check for existence of '$opt' in body");
}
}
my $reseller_id = 1;
# first, we create a rewriteruleset
t, we create a rewriteruleset
$req = HTTP::Request->new('POST', $uri.'/api/rewriterulesets/');
$req->header('Content-Type' => 'application/json');
$req->header('Prefer' => 'return=representation');
@ -104,92 +186,6 @@ my @allrules = ();
is($err->{code}, "422", "check error code in body");
like($err->{message}, qr/Invalid 'reseller_id'/, "check error message in body");
# try to create rule with invalid set_id
$req = HTTP::Request->new('POST', $uri.'/api/rewriterules/');
$req->header('Content-Type' => 'application/json');
$req->content(JSON::to_json({
set_id => 999999,
description => "test rule $t",
direction => "in",
field => "caller",
match_pattern => "test pattern $t",
replace_pattern => "test_replace_$t",
}));
$res = $ua->request($req);
is($res->code, 422, "create rule with invalid set_id");
$err = JSON::from_json($res->decoded_content);
is($err->{code}, "422", "check error code in body");
like($err->{message}, qr/Invalid 'set_id'/, "check error message in body");
# try to create rule with negative set_id
$req = HTTP::Request->new('POST', $uri.'/api/rewriterules/');
$req->header('Content-Type' => 'application/json');
$req->content(JSON::to_json({
set_id => -100,
description => "test rule $t",
direction => "in",
field => "caller",
match_pattern => "test pattern $t",
replace_pattern => "test_replace_$t",
}));
$res = $ua->request($req);
is($res->code, 422, "create rule with negative set_id");
$err = JSON::from_json($res->decoded_content);
is($err->{code}, "422", "check error code in body");
like($err->{message}, qr/(Invalid|Validation failed).*'set_id'/, "check error message in body");
# try to create rule with missing match_pattern
$req = HTTP::Request->new('POST', $uri.'/api/rewriterules/');
$req->header('Content-Type' => 'application/json');
$req->content(JSON::to_json({
set_id => $rewriteruleset_id,
description => "test rule $t",
direction => "in",
field => "caller",
#match_pattern => "test pattern $t",
replace_pattern => "test_replace_$t",
}));
$res = $ua->request($req);
is($res->code, 422, "create rule with missing match_pattern");
$err = JSON::from_json($res->decoded_content);
is($err->{code}, "422", "check error code in body");
like($err->{message}, qr/field='match_pattern'/, "check error message in body");
# try to create rule with invalid direction and field
$req = HTTP::Request->new('POST', $uri.'/api/rewriterules/');
$req->header('Content-Type' => 'application/json');
$req->content(JSON::to_json({
set_id => $rewriteruleset_id,
description => "test rule $t",
direction => "foo",
field => "bar",
match_pattern => "test pattern $t",
replace_pattern => "test_replace_$t",
}));
$res = $ua->request($req);
is($res->code, 422, "create rule with invalid direction and field");
$err = JSON::from_json($res->decoded_content);
is($err->{code}, "422", "check error code in body");
like($err->{message}, qr/field='direction'/, "check error message in body");
like($err->{message}, qr/field='field'/, "check error message in body");
# try to create rule without set_id
$req = HTTP::Request->new('POST', $uri.'/api/rewriterules/');
$req->header('Content-Type' => 'application/json');
$req->content(JSON::to_json({
#set_id => $rewriteruleset_id,
description => "test rule $t",
direction => "in",
field => "caller",
match_pattern => "test pattern $t",
replace_pattern => "test_replace_$t",
}));
$res = $ua->request($req);
is($res->code, 422, "create rule without set_id");
$err = JSON::from_json($res->decoded_content);
is($err->{code}, "422", "check error code in body");
like($err->{message}, qr/Required: 'set_id'|set_id.*required/, "check error message in body");
# iterate over rules collection to check next/prev links and status
my $nexturi = $uri.'/api/rewriterules/?page=1&rows=5';
do {

Loading…
Cancel
Save