diff --git a/lib/NGCP/Panel/Controller/API/Root.pm b/lib/NGCP/Panel/Controller/API/Root.pm index e11acbf073..0484920ed3 100644 --- a/lib/NGCP/Panel/Controller/API/Root.pm +++ b/lib/NGCP/Panel/Controller/API/Root.pm @@ -53,8 +53,9 @@ sub GET : Allow { next if(exists $blacklist->{$mod}); my $rel = lc $mod; my $full_mod = 'NGCP::Panel::Controller::API::'.$mod; + my $full_item_mod = 'NGCP::Panel::Controller::API::'.$mod.'Item'; - my $role = $full_mod->config->{action}->{GET}->{AllowedRole}; + my $role = $full_mod->config->{action}->{OPTIONS}->{AllowedRole}; if(ref $role eq "ARRAY") { next unless grep @{ $role }, $c->user->roles; } else { @@ -65,6 +66,9 @@ sub GET : Allow { if($full_mod->can('query_params')) { $query_params = $full_mod->query_params; } + my $actions = [ keys %{ $full_mod->config->{action} } ]; + my $item_actions = [ keys %{ $full_item_mod->config->{action} } ]; + my $form = $full_mod->get_form($c); $c->stash->{collections}->{$rel} = { @@ -72,7 +76,10 @@ sub GET : Allow { description => $full_mod->api_description, fields => $form ? $self->get_collection_properties($form) : [], query_params => $query_params, + actions => $actions, + item_actions => $item_actions, }; + } $c->stash(template => 'api/root.tt'); diff --git a/lib/NGCP/Panel/Controller/API/VoicemailRecordings.pm b/lib/NGCP/Panel/Controller/API/VoicemailRecordings.pm new file mode 100644 index 0000000000..28829f7e7a --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/VoicemailRecordings.pm @@ -0,0 +1,71 @@ +package NGCP::Panel::Controller::API::VoicemailRecordings; +use Sipwise::Base; +use namespace::sweep; +use boolean qw(true); +use Data::HAL qw(); +use Data::HAL::Link qw(); +use HTTP::Headers qw(); +use HTTP::Status qw(:constants); +use MooseX::ClassAttribute qw(class_has); +use NGCP::Panel::Utils::DateTime; +use Path::Tiny qw(path); +use Safe::Isa qw($_isa); +BEGIN { extends 'Catalyst::Controller::ActionRole'; } +require Catalyst::ActionRole::ACL; +require Catalyst::ActionRole::CheckTrailingSlash; +require Catalyst::ActionRole::HTTPMethods; +require Catalyst::ActionRole::RequireSSL; + +class_has 'api_description' => ( + is => 'ro', + isa => 'Str', + default => + 'Defines the actual recording of voicemail messages. It is referred to by the ngcp:voicemails relation. A GET on an item returns the binary blob of the recording with Content-Type "audio/x-wav".', +); + +with 'NGCP::Panel::Role::API::VoicemailRecordings'; + +class_has('resource_name', is => 'ro', default => 'voicemailrecordings'); +class_has('dispatch_path', is => 'ro', default => '/api/voicemailrecordings/'); +class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-voicemailrecordings'); + +__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(HTTPMethods)], +); + +sub auto :Private { + my ($self, $c) = @_; + + $self->set_body($c); + $self->log_request($c); +} + +sub OPTIONS :Allow { + my ($self, $c) = @_; + my $allowed_methods = $self->allowed_methods; + $c->response->headers(HTTP::Headers->new( + Allow => $allowed_methods->join(', '), + 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); +} + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Controller/API/VoicemailRecordingsItem.pm b/lib/NGCP/Panel/Controller/API/VoicemailRecordingsItem.pm new file mode 100644 index 0000000000..b5ae962892 --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/VoicemailRecordingsItem.pm @@ -0,0 +1,83 @@ +package NGCP::Panel::Controller::API::VoicemailRecordingsItem; +use Sipwise::Base; +use namespace::sweep; +use HTTP::Headers qw(); +use HTTP::Status qw(:constants); +use MooseX::ClassAttribute qw(class_has); +use NGCP::Panel::Utils::DateTime; +use NGCP::Panel::Utils::ValidateJSON qw(); +use Path::Tiny qw(path); +use Safe::Isa qw($_isa); +BEGIN { extends 'Catalyst::Controller::ActionRole'; } +require Catalyst::ActionRole::ACL; +require Catalyst::ActionRole::HTTPMethods; +require Catalyst::ActionRole::RequireSSL; + +with 'NGCP::Panel::Role::API::VoicemailRecordings'; + +class_has('resource_name', is => 'ro', default => 'voicemailrecordings'); +class_has('dispatch_path', is => 'ro', default => '/api/voicemailrecordings/'); +class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-voicemailrecordings'); + +__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 } + }, + action_roles => [qw(HTTPMethods)], +); + +sub auto :Private { + my ($self, $c) = @_; + + $self->set_body($c); + $self->log_request($c); +} + +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, voicemailrecording => $item); + + $c->response->header ('Content-Disposition' => 'attachment; filename="' . $self->resource_name . '-' . $item->id . '.wav"'); + $c->response->content_type('audio/x-wav'); + $c->response->body($item->recording); + 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; + $c->response->headers(HTTP::Headers->new( + Allow => $allowed_methods->join(', '), + Accept_Patch => 'application/json-patch+json', + )); + $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); +} + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Controller/API/Voicemails.pm b/lib/NGCP/Panel/Controller/API/Voicemails.pm new file mode 100644 index 0000000000..c561e9893f --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/Voicemails.pm @@ -0,0 +1,147 @@ +package NGCP::Panel::Controller::API::Voicemails; +use Sipwise::Base; +use namespace::sweep; +use boolean qw(true); +use Data::HAL qw(); +use Data::HAL::Link qw(); +use HTTP::Headers qw(); +use HTTP::Status qw(:constants); +use MooseX::ClassAttribute qw(class_has); +use NGCP::Panel::Utils::DateTime; +use Path::Tiny qw(path); +use Safe::Isa qw($_isa); +BEGIN { extends 'Catalyst::Controller::ActionRole'; } +require Catalyst::ActionRole::ACL; +require Catalyst::ActionRole::CheckTrailingSlash; +require Catalyst::ActionRole::HTTPMethods; +require Catalyst::ActionRole::RequireSSL; + +class_has 'api_description' => ( + is => 'ro', + isa => 'Str', + default => + 'Defines the meta information like duration, callerid etc for voicemail recordings. The actual recordings can be fetched via the related ngcp:voicemailrecordings relation.', +); + +class_has 'query_params' => ( + is => 'ro', + isa => 'ArrayRef', + default => sub {[ + { + param => 'subscriber_id', + description => 'Filter for voicemails belonging to a specific subscriber', + query => { + first => sub { + my $q = shift; + { 'voip_subscriber.id' => $q }; + }, + second => sub { join => 'foo' }, + }, + }, + ]}, +); + +with 'NGCP::Panel::Role::API::Voicemails'; + +class_has('resource_name', is => 'ro', default => 'voicemails'); +class_has('dispatch_path', is => 'ro', default => '/api/voicemails/'); +class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-voicemails'); + +__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(HTTPMethods)], +); + +sub auto :Private { + my ($self, $c) = @_; + + $self->set_body($c); + $self->log_request($c); +} + +sub GET :Allow { + my ($self, $c) = @_; + my $page = $c->request->params->{page} // 1; + my $rows = $c->request->params->{rows} // 10; + { + my $items = $self->item_rs($c); + my $total_count = int($items->count); + $items = $items->search(undef, { + page => $page, + rows => $rows, + }); + my (@embedded, @links); + for my $item ($items->search({}, {order_by => {-asc => 'me.id'}})->all) { + push @embedded, $self->hal_from_item($c, $item); + push @links, Data::HAL::Link->new( + relation => 'ngcp:'.$self->resource_name, + href => sprintf('/%s%d', $c->request->path, $item->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', $c->request->path, $page, $rows)); + if(($total_count / $rows) > $page ) { + push @links, Data::HAL::Link->new(relation => 'next', href => sprintf('/%s?page=%d&rows=%d', $c->request->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; + $c->response->headers(HTTP::Headers->new( + Allow => $allowed_methods->join(', '), + 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); +} + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Controller/API/VoicemailsItem.pm b/lib/NGCP/Panel/Controller/API/VoicemailsItem.pm new file mode 100644 index 0000000000..aa3753de36 --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/VoicemailsItem.pm @@ -0,0 +1,192 @@ +package NGCP::Panel::Controller::API::VoicemailsItem; +use Sipwise::Base; +use namespace::sweep; +use HTTP::Headers qw(); +use HTTP::Status qw(:constants); +use MooseX::ClassAttribute qw(class_has); +use NGCP::Panel::Utils::DateTime; +use NGCP::Panel::Utils::ValidateJSON qw(); +use Path::Tiny qw(path); +use Safe::Isa qw($_isa); +BEGIN { extends 'Catalyst::Controller::ActionRole'; } +require Catalyst::ActionRole::ACL; +require Catalyst::ActionRole::HTTPMethods; +require Catalyst::ActionRole::RequireSSL; + +with 'NGCP::Panel::Role::API::Voicemails'; + +class_has('resource_name', is => 'ro', default => 'voicemails'); +class_has('dispatch_path', is => 'ro', default => '/api/voicemails/'); +class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-voicemails'); + +__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 } + }, + action_roles => [qw(HTTPMethods)], +); + +sub auto :Private { + my ($self, $c) = @_; + + $self->set_body($c); + $self->log_request($c); +} + +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, voicemail => $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"|; + s/rel=self/rel="item self"/; + $_ + } $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; + $c->response->headers(HTTP::Headers->new( + Allow => $allowed_methods->join(', '), + Accept_Patch => 'application/json-patch+json', + )); + $c->response->content_type('application/json'); + $c->response->body(JSON::to_json({ methods => $allowed_methods })."\n"); + return; +} + +sub PATCH :Allow { + my ($self, $c, $id) = @_; + 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', + ); + last unless $json; + + my $item = $self->item_by_id($c, $id); + last unless $self->resource_exists($c, voicemail => $item); + my $form = $self->get_form($c); + my $old_resource = $self->resource_from_item($c, $item, $form); + my $resource = $self->apply_patch($c, $old_resource, $json); + last unless $resource; + + $item = $self->update_item($c, $item, $old_resource, $resource, $form); + last unless $item; + + $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, $item, $form); + 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 PUT :Allow { + my ($self, $c, $id) = @_; + my $guard = $c->model('DB')->txn_scope_guard; + { + my $preference = $self->require_preference($c); + last unless $preference; + + my $item = $self->item_by_id($c, $id); + last unless $self->resource_exists($c, voicemail => $item); + 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->resource_from_item($c, $item, $form); + + $item = $self->update_item($c, $item, $old_resource, $resource, $form); + last unless $item; + + $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, $item, $form); + 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) = @_; + + my $guard = $c->model('DB')->txn_scope_guard; + { + my $item = $self->item_by_id($c, $id); + last unless $self->resource_exists($c, voicemail => $item); + + $item->delete; + + $guard->commit; + + $c->response->status(HTTP_NO_CONTENT); + $c->response->body(q()); + } + return; +} + +sub end : Private { + my ($self, $c) = @_; + + $self->log_response($c); +} + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/Voicemail/API.pm b/lib/NGCP/Panel/Form/Voicemail/API.pm new file mode 100644 index 0000000000..83c8aa0014 --- /dev/null +++ b/lib/NGCP/Panel/Form/Voicemail/API.pm @@ -0,0 +1,86 @@ +package NGCP::Panel::Form::Voicemail::API; + +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler'; +use Moose::Util::TypeConstraints; + +use HTML::FormHandler::Widget::Block::Bootstrap; + +has '+widget_wrapper' => ( default => 'Bootstrap' ); +has_field 'submitid' => ( type => 'Hidden' ); +sub build_render_list {[qw/submitid fields actions/]} +sub build_form_element_class { [qw/form-horizontal/] } + +has_field 'pin' => ( + type => 'Text', + label => 'PIN', + minlength => 4, + maxlength => 31, + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The PIN used to enter the IVR menu from external numbers.'] + }, +); + +has_field 'email' => ( + type => 'Email', + label => 'Email Address', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The email address where to send notifications and the recordings.'] + }, +) + +has_field 'delete' => ( + type => 'Boolean', + label => 'Delete Messages', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['Delete voicemail recordings from the mailbox after delivering them via email.'] + }, +); + +has_field 'attach' => ( + type => 'Boolean', + label => 'Attach Recording', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['Attach recordings when delivering them via email. Must be set if delete flag is set'] + }, +); + +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/pin email attach delete/], +); + +has_block 'actions' => ( + tag => 'div', + class => [qw/modal-footer/], + render_list => [qw/save/], +); + +sub validate { + my $self = shift; + my $attach = $self->field('attach')->value; + my $delete = $self->field('delete')->value; + if($delete && !$attach) { + $self->field('attach')->add_error('Must be set if delete is set'); + } +} + + +1; +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Form/Voicemail/Meta.pm b/lib/NGCP/Panel/Form/Voicemail/Meta.pm new file mode 100644 index 0000000000..48005df5d5 --- /dev/null +++ b/lib/NGCP/Panel/Form/Voicemail/Meta.pm @@ -0,0 +1,97 @@ +package NGCP::Panel::Form::Voicemail::Meta; + +use HTML::FormHandler::Moose; +extends 'HTML::FormHandler'; +use Moose::Util::TypeConstraints; + +use HTML::FormHandler::Widget::Block::Bootstrap; + +has '+widget_wrapper' => ( default => 'Bootstrap' ); +has_field 'submitid' => ( type => 'Hidden' ); +sub build_render_list {[qw/submitid fields actions/]} +sub build_form_element_class { [qw/form-horizontal/] } + +has_field 'subscriber_id' => ( + type => 'PosInteger', + label => 'Subscriber ID', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The subscriber id the message belongs to.'] + }, +); + +has_field 'duration' => ( + type => 'PosInteger', + label => 'Duration', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The duration of the message.'] + }, +); + +has_field 'time' => ( + type => 'Text', + label => 'Time', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The time the message was recorded.'] + }, +); + +has_field 'caller' => ( + type => 'Text', + label => 'Caller', + required => 0, + element_attr => { + rel => ['tooltip'], + title => ['The caller ID who left the message.'] + }, +); + +has_field 'folder' => ( + type => 'Select', + label => 'Folder', + required => 1, + element_attr => { + rel => ['tooltip'], + title => ['The folder the message is currently in (one of INBOX, Old, Work, Friends, Family, Cust1-Cust6)'] + }, + options => [ + { label => 'INBOX', value => 'INBOX' }, + { label => 'Old', value => 'Old' }, + { label => 'Work', value => 'Work' }, + { label => 'Friends', value => 'Friends' }, + { label => 'Family', value => 'Family' }, + { label => 'Cust1', value => 'Cust1' }, + { label => 'Cust2', value => 'Cust2' }, + { label => 'Cust3', value => 'Cust3' }, + { label => 'Cust4', value => 'Cust4' }, + { label => 'Cust5', value => 'Cust5' }, + { label => 'Cust6', value => 'Cust6' }, + ] +); + +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/folder/], +); + +has_block 'actions' => ( + tag => 'div', + class => [qw/modal-footer/], + render_list => [qw/save/], +); + +1; +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Role/API/VoicemailRecordings.pm b/lib/NGCP/Panel/Role/API/VoicemailRecordings.pm new file mode 100644 index 0000000000..5e2665fc0b --- /dev/null +++ b/lib/NGCP/Panel/Role/API/VoicemailRecordings.pm @@ -0,0 +1,41 @@ +package NGCP::Panel::Role::API::VoicemailRecordings; +use Moose::Role; +use Sipwise::Base; +with 'NGCP::Panel::Role::API' => { + -alias =>{ item_rs => '_item_rs', }, + -excludes => [ 'item_rs' ], +}; + +sub item_rs { + my ($self, $c) = @_; + + my $item_rs = $c->model('DB')->resultset('voicemail_spool')->search({ + duration => { '!=' => '' }, + 'voip_subscriber.id' => { '!=' => undef }, + },{ + join => { mailboxuser => { provisioning_voip_subscriber => 'voip_subscriber' } } + }); + if($c->user->roles eq "admin") { + } elsif($c->user->roles eq "reseller") { + $item_rs = $item_rs->search({ + 'contact.reseller_id' => $c->user->reseller_id + },{ + join => { mailboxuser => { provisioning_voip_subscriber => { voip_subscriber => { contract => 'contact' } } } } + }); + } + return $item_rs; +} + +sub get_form { + my ($self, $c) = @_; + return; +} + +sub item_by_id { + my ($self, $c, $id) = @_; + my $item_rs = $self->item_rs($c); + return $item_rs->find($id); +} + +1; +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Role/API/Voicemails.pm b/lib/NGCP/Panel/Role/API/Voicemails.pm new file mode 100644 index 0000000000..a1df38bb54 --- /dev/null +++ b/lib/NGCP/Panel/Role/API/Voicemails.pm @@ -0,0 +1,112 @@ +package NGCP::Panel::Role::API::Voicemails; +use Moose::Role; +use Sipwise::Base; +with 'NGCP::Panel::Role::API' => { + -alias =>{ item_rs => '_item_rs', }, + -excludes => [ 'item_rs' ], +}; + +use boolean qw(true); +use TryCatch; +use Data::HAL qw(); +use Data::HAL::Link qw(); +use HTTP::Status qw(:constants); +use NGCP::Panel::Form::Voicemail::Meta; + +sub item_rs { + my ($self, $c) = @_; + + my $item_rs = $c->model('DB')->resultset('voicemail_spool')->search({ + duration => { '!=' => '' }, + 'voip_subscriber.id' => { '!=' => undef }, + },{ + join => { mailboxuser => { provisioning_voip_subscriber => 'voip_subscriber' } } + }); + if($c->user->roles eq "admin") { + } elsif($c->user->roles eq "reseller") { + $item_rs = $item_rs->search({ + 'contact.reseller_id' => $c->user->reseller_id + },{ + join => { mailboxuser => { provisioning_voip_subscriber => { voip_subscriber => { contract => 'contact' } } } } + }); + } + return $item_rs; +} + +sub get_form { + my ($self, $c) = @_; + return NGCP::Panel::Form::Voicemail::Meta->new; +} + +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->mailboxuser->provisioning_voip_subscriber->voip_subscriber->id)), + Data::HAL::Link->new(relation => 'ngcp:voicemailrecordings', href => sprintf("/api/voicemailrecordings/%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) = @_; + + $form //= $self->get_form($c); + + my %resource = (); + $resource{id} = int($item->id); + $resource{duration} = $item->duration->is_int ? int($item->duration) : 0; + $resource{time} = "" . $item->origtime; + $resource{caller} = $item->callerid; + $resource{subscriber_id} = int($item->mailboxuser->provisioning_voip_subscriber->voip_subscriber->id); + + # type is last item of path like /var/spool/asterisk/voicemail/default/uuid/INBOX + my @p = split '/', $item->dir; + $resource{folder} = pop @p; + + return \%resource; +} + +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) = @_; + + $form //= $self->get_form($c); + return unless $self->validate_form( + c => $c, + form => $form, + resource => $resource, + ); + + my $f = $resource->{folder}; + my $upresource = {}; + $upresource->{dir} = $item->dir; + $upresource->{dir} =~ s/\/[^\/]+$/\/$f/; + + $item->update($upresource); + + return $item; +} + +1; +# vim: set tabstop=4 expandtab: diff --git a/share/templates/api/root/collection.tt b/share/templates/api/root/collection.tt index b5aca755d8..2441eaf723 100644 --- a/share/templates/api/root/collection.tt +++ b/share/templates/api/root/collection.tt @@ -12,6 +12,31 @@ [% col.description %] +Collection Actions +Allowed methods for the collection as in METHOD [% uri %] +[% UNLESS col.actions.size -%] +None. +[% ELSE -%] + +[% END -%] + +Item Actions +Allowed methods for a collection item as in METHOD [% uri %]id +[% UNLESS col.item_actions.size -%] +None. +[% ELSE -%] + +[% END -%] + + Properties [% UNLESS col.fields.size -%] @@ -39,6 +64,7 @@ None. Examples
+[% IF col.actions.grep('^OPTIONS$').size -%] Request available HTTP methods on the URI

[% @@ -55,7 +81,9 @@ Accept-Post: application/hal+json; profile="http://purl.org/sipwise/ngcp-api/#re INCLUDE helpers/api_req_res.tt request=request response=response level=level+3; -%]

+[% END -%] +[% IF col.actions.grep('^GET$').size -%] Request the entire [% id %] collection

@@ -144,7 +172,9 @@ Content-Type: application/hal+json; profile="http://purl.org/sipwise/ngcp-api/" INCLUDE helpers/api_req_res.tt request=request response=response level=level+3; -%]

+[% END -%] +[% IF col.item_actions.grep('^GET$').size -%] Request a specific [% id %] item

@@ -203,7 +233,9 @@ Link: ; rel="item self" INCLUDE helpers/api_req_res.tt request=request response=response level=level+3; -%]

+[% END -%] +[% IF col.actions.grep('^POST$').size -%] Create a new [% id %] item

[% @@ -262,7 +294,9 @@ Location: /api/' _ id _ '/2'; INCLUDE helpers/api_req_res.tt request=request response=response level=level+3; %]

+[% END -%] +[% IF col.item_actions.grep('^PUT$').size -%] Update an existing [% id %] item

[% @@ -322,7 +356,9 @@ Preference-Applied: return=minimal'; INCLUDE helpers/api_req_res.tt request=request response=response level=level+3; -%]

+[% END -%] +[% IF col.item_actions.grep('^PATCH$').size -%] Update specific fields of an existing [% id %] item

[% @@ -442,6 +478,25 @@ Preference-Applied: return=minimal'; INCLUDE helpers/api_req_res.tt request=request response=response level=level+3; -%]

+[% END -%] + +[% IF col.item_actions.grep('^DELETE$').size -%] +Delete a specific [% id %] item +

+ +[% + cmd = 'curl -i -X DELETE -H \'Connection: close\' --cert NGCP-API-client-certificate.pem --cacert ca-cert.pem \'https://example.org:1443/api/' _ id _ '/1\''; + INCLUDE helpers/api_command.tt cmd=cmd level=level+3; + + request = +'DELETE /api/' _ id _ '/1 HTTP/1.1'; + response = +'HTTP/1.1 204 No Content'; + + INCLUDE helpers/api_req_res.tt request=request response=response level=level+3; +-%] +

+[% END -%]
[% # vim: set tabstop=4 syntax=html expandtab: -%]