MT#19795 create rtcapps REST api endpoint

Change-Id: Ic3a7d41865f464e3f51b08c5506e1ea59d78d29b
changes/27/6727/3
Gerhard Jungwirth 10 years ago
parent 98b6d75a18
commit 4d8d5e8bf4

@ -0,0 +1,137 @@
package NGCP::Panel::Controller::API::RtcApps;
use NGCP::Panel::Utils::Generic qw(:all);
use TryCatch;
use boolean qw(true);
use Data::HAL qw();
use Data::HAL::Link qw();
use HTTP::Headers qw();
use HTTP::Status qw(:constants);
require Catalyst::ActionRole::ACL;
require Catalyst::ActionRole::CheckTrailingSlash;
require NGCP::Panel::Role::HTTPMethods;
require Catalyst::ActionRole::RequireSSL;
sub allowed_methods{
return [qw/GET OPTIONS HEAD/];
}
sub api_description {
return 'Show a collection of RTC apps, belonging to a specific reseller.';
};
sub query_params {
return [];
}
use parent qw/Catalyst::Controller NGCP::Panel::Role::API::RtcApps/;
sub resource_name{
return 'rtcapps';
}
sub dispatch_path{
return '/api/rtcapps/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-rtcapps';
}
__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 },
},
action_roles => [qw(+NGCP::Panel::Role::HTTPMethods)],
);
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 $resellers = $self->item_rs($c);
(my $total_count, $resellers) = $self->paginate_order_collection($c, $resellers);
my (@embedded, @links);
for my $reseller ($resellers->all) {
push @embedded, $self->hal_from_item($c, $reseller);
push @links, Data::HAL::Link->new(
relation => 'ngcp:'.$self->resource_name,
href => sprintf('%s%d', $self->dispatch_path, $reseller->id),
);
}
push @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 => 'profile', href => 'http://purl.org/sipwise/ngcp-api/'),
Data::HAL::Link->new(relation => 'self', href => sprintf('%s?page=%s&rows=%s', $self->dispatch_path, $page, $rows));
if(($total_count / $rows) > $page ) {
push @links, Data::HAL::Link->new(relation => 'next', href => sprintf('%s?page=%d&rows=%d', $self->dispatch_path, $page + 1, $rows));
}
if($page > 1) {
push @links, Data::HAL::Link->new(relation => 'prev', href => sprintf('/%s?page=%d&rows=%d', $c->request->path, $page - 1, $rows));
}
my $hal = Data::HAL->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 end : Private {
my ($self, $c) = @_;
$self->log_response($c);
return 1;
}
no Moose;
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,235 @@
package NGCP::Panel::Controller::API::RtcAppsItem;
use NGCP::Panel::Utils::Generic qw(:all);
use Data::HAL qw();
use Data::HAL::Link qw();
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 PATCH PUT/];
}
use parent qw/Catalyst::Controller NGCP::Panel::Role::API::RtcApps/;
sub resource_name{
return 'rtcapps';
}
sub dispatch_path{
return '/api/rtcapps/';
}
sub relation{
return 'http://purl.org/sipwise/ngcp-api/#rel-rtcapps';
}
sub journal_query_params {
my($self,$query_params) = @_;
return $self->get_journal_query_params($query_params);
}
__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 }),
@{ __PACKAGE__->get_journal_action_config(__PACKAGE__->resource_name,{
ACLDetachTo => '/api/root/invalid_user',
AllowedRole => [qw/admin reseller/],
Does => [qw(ACL RequireSSL)],
}) },
},
action_roles => [qw(+NGCP::Panel::Role::HTTPMethods)],
);
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 $item = $self->item_by_id($c, $id);
last unless $self->resource_exists($c, reseller => $item);
my $hal = $self->hal_from_item($c, $item);
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 PUT :Allow {
my ($self, $c, $id) = @_;
my $schema = $c->model('DB');
my $guard = $schema->txn_scope_guard;
{
my $preference = $self->require_preference($c);
last unless $preference;
my $reseller = $self->item_by_id($c, $id);
last unless $self->resource_exists($c, reseller => $reseller);
my $resource = $self->get_valid_put_data(
c => $c,
id => $id,
media_type => 'application/json',
);
last unless $resource;
my $form = $self->get_form($c);
my $old_resource = $self->hal_from_item($c, $reseller, 1)->resource;
$reseller = $self->update_item($c, $reseller, $old_resource, $resource, $form);
last unless $reseller;
my $hal = $self->hal_from_item($c, $reseller);
last unless $self->add_update_journal_item_hal($c,{ hal => $hal, id => $reseller->id });
$guard->commit;
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, $reseller);
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 PATCH :Allow {
my ($self, $c, $id) = @_;
my $schema = $c->model('DB');
my $guard = $schema->txn_scope_guard;
{
my $preference = $self->require_preference($c);
last unless $preference;
my $reseller = $self->item_by_id($c, $id);
last unless $self->resource_exists($c, reseller => $reseller);
my $json = $self->get_valid_patch_data(
c => $c,
id => $id,
media_type => 'application/json-patch+json',
ops => ["add", "replace", "copy", "remove"],
);
last unless $json;
my $form = $self->get_form($c);
my $old_resource = $self->hal_from_item($c, $reseller, 1)->resource;
my $resource = $self->apply_patch($c, $old_resource, $json);
last unless $resource;
$reseller = $self->update_item($c, $reseller, $old_resource, $resource, $form);
last unless $reseller;
my $hal = $self->hal_from_item($c, $reseller);
last unless $self->add_update_journal_item_hal($c,{ hal => $hal, id => $reseller->id });
$guard->commit;
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, $reseller);
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 item_base_journal :Journal {
my $self = shift @_;
return $self->handle_item_base_journal(@_);
}
sub journals_get :Journal {
my $self = shift @_;
return $self->handle_journals_get(@_);
}
sub journalsitem_get :Journal {
my $self = shift @_;
return $self->handle_journalsitem_get(@_);
}
sub journals_options :Journal {
my $self = shift @_;
return $self->handle_journals_options(@_);
}
sub journalsitem_options :Journal {
my $self = shift @_;
return $self->handle_journalsitem_options(@_);
}
sub journals_head :Journal {
my $self = shift @_;
return $self->handle_journals_head(@_);
}
sub journalsitem_head :Journal {
my $self = shift @_;
return $self->handle_journalsitem_head(@_);
}
sub end : Private {
my ($self, $c) = @_;
$self->log_response($c);
return 1;
}
no Moose;
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,22 @@
package NGCP::Panel::Form::Rtc::AppsAdmin;
use HTML::FormHandler::Moose;
extends 'NGCP::Panel::Form::Rtc::AppsReseller';
has_field 'reseller' => (
type => '+NGCP::Panel::Field::Reseller',
#validate_when_empty => 1,
element_attr => {
rel => ['tooltip'],
title => ['The reseller id this app belong to.'],
},
);
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/reseller rtc_user_id apps/],
);
1;
# vim: set tabstop=4 expandtab:

@ -0,0 +1,90 @@
package NGCP::Panel::Form::Rtc::AppsReseller;
use HTML::FormHandler::Moose;
use HTML::FormHandler::Widget::Block::Bootstrap;
extends 'HTML::FormHandler';
# with 'NGCP::Panel::Render::RepeatableJs'; # only used in API currently
has '+widget_wrapper' => ( default => 'Bootstrap' );
has_field 'submitid' => ( type => 'Hidden' );
sub build_render_list {return [qw/submitid fields actions/]}
sub build_form_element_class {return [qw(form-horizontal)]}
has_field 'rtc_user_id' => (
# readonly
type => 'Text',
required => 0,
element_attr => {
rel => ['tooltip'],
title => ['ID in the backend RTC API (readonly).'],
},
);
has_field 'apps' => (
type => 'Repeatable',
required => 0, #1,
setup_for_js => 1,
do_wrapper => 1,
do_label => 0,
tags => {
controls_div => 1,
},
wrapper_class => [qw/hfh-rep/],
element_attr => {
rel => ['tooltip'],
title => ['An array of objects with keys "name", "domain", "secret" and "api_key" to create RTC apps for this reseller'],
},
);
has_field 'apps.domain' => (
type => 'Text',
element_attr => {
rel => ['tooltip'],
title => ['Domain where the cdk is included.'],
},
);
has_field 'apps.name' => (
type => 'Text',
element_attr => {
rel => ['tooltip'],
title => ['Arbitrary text. Name of the app.'],
},
);
has_field 'apps.secret' => (
type => 'Text',
element_attr => {
rel => ['tooltip'],
title => ['The secret (readonly).'],
},
);
has_field 'apps.api_key' => (
type => 'Text',
element_attr => {
rel => ['tooltip'],
title => ['The API key (readonly).'],
},
);
has_field 'save' => (
type => 'Submit',
value => 'Save',
element_class => [qw/btn btn-primary/],
label => '',
);
has_block 'fields' => (
tag => 'div',
class => [qw/modal-body/],
render_list => [qw/rtc_user_id apps/],
);
has_block 'actions' => (
tag => 'div',
class => [qw/modal-footer/],
render_list => [qw/save/],
);
1;

@ -0,0 +1,134 @@
package NGCP::Panel::Role::API::RtcApps;
use NGCP::Panel::Utils::Generic qw(:all);
use parent 'NGCP::Panel::Role::API';
use boolean qw(true);
use TryCatch;
use Data::HAL qw();
use Data::HAL::Link qw();
use HTTP::Status qw(:constants);
use JSON::Types;
use NGCP::Panel::Form::Rtc::AppsAdmin;
use NGCP::Panel::Utils::Subscriber;
use NGCP::Panel::Utils::Rtc;
sub get_form {
my ($self, $c) = @_;
return NGCP::Panel::Form::Rtc::AppsAdmin->new;
}
sub hal_from_item {
my ($self, $c, $item, $include_id) = @_;
my $resource = { reseller_id => $item->id};
if ($item->rtc_user) {
my $rtc_user_id = $item->rtc_user->rtc_user_id;
$resource->{rtc_user_id} = $rtc_user_id if $include_id;
$resource->{apps} = NGCP::Panel::Utils::Rtc::get_rtc_apps(
rtc_user_id => $rtc_user_id,
config => $c->config,
include_id => $include_id,
err_code => sub {
$c->log->warn(shift); return;
});
} else {
}
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:resellers', href => sprintf("/api/resellers/%d", $item->id)),
Data::HAL::Link->new(relation => 'ngcp:resellers', href => sprintf("/api/rtcnetworks/%d", $item->id)),
$self->get_journal_relation_link($item->id),
],
relation => 'ngcp:'.$self->resource_name,
);
my $form = $self->get_form($c);
unless ($include_id) {
return unless $self->validate_form(
c => $c,
form => $form,
resource => $resource,
run => 0,
exceptions => ['rtc_user_id'],
);
}
$hal->resource($resource);
return $hal;
}
sub _item_rs {
my ($self, $c) = @_;
my $item_rs;
$item_rs = $c->model('DB')->resultset('resellers')
->search_rs(undef, {
prefetch => 'rtc_user',
});
if($c->user->roles eq "admin") {
} elsif($c->user->roles eq "reseller") {
$item_rs = $item_rs->search({
id => $c->user->reseller_id,
});
}
return $item_rs;
}
sub item_by_id {
my ($self, $c, $id) = @_;
my $item_rs = $self->item_rs($c);
return $item_rs->find($id);
}
sub update_item {
my ($self, $c, $item, $old_resource, $resource, $form) = @_;
my $reseller = $item;
if (ref $resource->{apps} ne "ARRAY") {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid field 'apps'. Must be an array.");
return;
}
$form //= $self->get_form($c);
return unless $self->validate_form(
c => $c,
form => $form,
resource => $resource,
);
NGCP::Panel::Utils::Rtc::modify_rtc_apps(
old_resource => $old_resource,
resource => $resource,
config => $c->config,
reseller_item => $reseller,
err_code => sub {
$c->log->warn(shift); return;
});
try {
} catch($e) {
$c->log->error("failed to update rtcapps: $e");
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to update rtcapps.");
return;
};
return $reseller;
}
1;
# vim: set tabstop=4 expandtab:

@ -173,6 +173,119 @@ sub _delete_rtc_user {
return;
}
sub get_rtc_apps {
my %params = @_;
my ($rtc_user_id, $config, $include_id, $err_code) =
@params{qw/rtc_user_id config include_id err_code/};
if (!defined $err_code || ref $err_code ne 'CODE') {
$err_code = sub { return 0; };
}
my $comx = NGCP::Panel::Utils::ComxAPIClient->new(
host => $config->{rtc}{schema}.'://'.
$config->{rtc}{host}.':'.$config->{rtc}{port}.
$config->{rtc}{path},
);
$comx->login(
$config->{rtc}{user},
$config->{rtc}{pass},
$config->{rtc}{host}.':'.$config->{rtc}{port});
if ($comx->login_status->{code} != 200) {
return unless &{$err_code}(
'Rtc Login failed. Check config settings.');
}
my $apps_resp = $comx->get_apps_by_user_id($rtc_user_id);
my $apps = $apps_resp->{data};
unless (defined $apps && 'ARRAY' eq ref $apps && @{ $apps }) {
return unless &{$err_code}(
'Fetching apps failed. Code: ' . $apps_resp->{code});
}
my $res = [map {{
domain =>$_->{domain},
name => $_->{name},
secret => $_->{secret},
api_key => $_->{apiKey}, # todo: which spelling do we use?
$include_id ? (id => $_->{id}) : (),
}} @{ $apps }];
return $res;
}
sub modify_rtc_apps {
my %params = @_;
my ($old_resource, $resource, $config, $reseller_item, $err_code) =
@params{qw/old_resource resource config reseller_item err_code/};
#TODO: stub, to be done
if (!defined $err_code || ref $err_code ne 'CODE') {
$err_code = sub { return 0; };
}
if ((!defined $old_resource) || (!defined $resource)) { # can only modify (no create/delete) the whole resource
return unless &{$err_code}(
'Cannot Modify rtc app. Old or new resource missing.');
}
my $comx = NGCP::Panel::Utils::ComxAPIClient->new(
host => $config->{rtc}{schema}.'://'.
$config->{rtc}{host}.':'.$config->{rtc}{port}.
$config->{rtc}{path},
);
$comx->login(
$config->{rtc}{user},
$config->{rtc}{pass},
$config->{rtc}{host}.':'.$config->{rtc}{port});
if ($comx->login_status->{code} != 200) {
return unless &{$err_code}(
'Rtc Login failed. Check config settings.');
}
my (@deleted, @new);
for my $a (@{ $resource->{apps} }) {
my $app_name = $a->{name};
my ($old_app) = grep {$app_name eq $_->{name}} @{ $old_resource->{apps} };
if (!defined $old_app) {
push @new, $a;
} else {
if ($a->{domain} ne $old_app->{domain}) {
push @deleted, $old_app;
push @new, $a;
}
}
}
for my $a (@{ $old_resource->{apps} }) {
my $app_name = $a->{name};
my ($new_app) = grep {$app_name eq $_->{name}} @{ $resource->{apps} };
if (!defined $new_app) {
push @deleted, $a;
}
}
for my $app (@deleted) {
my $a_response = $comx->delete_app($app->{id});
if ($a_response->{code} != 200) {
return unless &{$err_code}(
'Deleting rtc app failed. Error code: ' . $a_response->{code});
}
}
for my $app (@new) {
my $a_response = $comx->create_app(
$app->{name},
$app->{domain},
$old_resource->{rtc_user_id},
);
if ($a_response->{code} != 201) {
return unless &{$err_code}(
'Creating rtc app failed. Error code: ' . $a_response->{code});
}
}
return;
}
sub get_rtc_networks {
my %params = @_;
my ($rtc_user_id, $config, $reseller_item, $include_id, $err_code) =

Loading…
Cancel
Save