diff --git a/lib/NGCP/Panel/Controller/API/CFTimeSetsItem.pm b/lib/NGCP/Panel/Controller/API/CFTimeSetsItem.pm index 14ce2da8ab..fc8443bb17 100644 --- a/lib/NGCP/Panel/Controller/API/CFTimeSetsItem.pm +++ b/lib/NGCP/Panel/Controller/API/CFTimeSetsItem.pm @@ -22,16 +22,23 @@ class_has('resource_name', is => 'ro', default => 'cftimesets'); class_has('dispatch_path', is => 'ro', default => '/api/cftimesets/'); class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-cftimesets'); +class_has(@{ __PACKAGE__->get_journal_query_params() }); + __PACKAGE__->config( action => { - map { $_ => { + (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__->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(HTTPMethods)], ); @@ -110,6 +117,9 @@ sub PATCH :Allow { $tset = $self->update_item($c, $tset, $old_resource, $resource, $form); last unless $tset; + my $hal = $self->hal_from_item($c, $tset, "timesets"); + last unless $self->add_update_journal_item_hal($c,$hal); + $guard->commit; if ('minimal' eq $preference) { @@ -117,7 +127,7 @@ sub PATCH :Allow { $c->response->header(Preference_Applied => 'return=minimal'); $c->response->body(q()); } else { - my $hal = $self->hal_from_item($c, $tset, "timesets"); + #my $hal = $self->hal_from_item($c, $tset, "timesets"); my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new( $hal->http_headers, ), $hal->as_json); @@ -149,6 +159,9 @@ sub PUT :Allow { my $form = $self->get_form($c); $tset = $self->update_item($c, $tset, $old_resource, $resource, $form); last unless $tset; + + my $hal = $self->hal_from_item($c, $tset, "destinationsets"); + last unless $self->add_update_journal_item_hal($c,$hal); $guard->commit; @@ -157,7 +170,7 @@ sub PUT :Allow { $c->response->header(Preference_Applied => 'return=minimal'); $c->response->body(q()); } else { - my $hal = $self->hal_from_item($c, $tset, "destinationsets"); + #my $hal = $self->hal_from_item($c, $tset, "destinationsets"); my $response = HTTP::Response->new(HTTP_OK, undef, HTTP::Headers->new( $hal->http_headers, ), $hal->as_json); @@ -190,6 +203,41 @@ sub DELETE :Allow { 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) = @_; diff --git a/lib/NGCP/Panel/Controller/API/Root.pm b/lib/NGCP/Panel/Controller/API/Root.pm index b49e0d5891..bc9c9f6f4b 100644 --- a/lib/NGCP/Panel/Controller/API/Root.pm +++ b/lib/NGCP/Panel/Controller/API/Root.pm @@ -14,6 +14,8 @@ require Catalyst::ActionRole::CheckTrailingSlash; require Catalyst::ActionRole::HTTPMethods; require Catalyst::ActionRole::RequireSSL; +use NGCP::Panel::Utils::Journal qw(); + with 'NGCP::Panel::Role::API'; class_has('dispatch_path', is => 'ro', default => '/api/'); @@ -79,7 +81,9 @@ sub GET : Allow { } else { $actions = [ keys %{ $full_mod->config->{action} } ]; } + my $uri = "/api/$rel/"; my $item_actions = []; + my $journal_resource_config = {}; if($full_item_mod->can('config')) { if($c->user->read_only) { foreach my $m(keys %{ $full_item_mod->config->{action} }) { @@ -87,7 +91,50 @@ sub GET : Allow { push @{ $item_actions }, $m; } } else { - $item_actions = [ keys %{ $full_item_mod->config->{action} } ]; + foreach my $m(keys %{ $full_item_mod->config->{action} }) { + next unless $m =~ /^(GET|HEAD|OPTIONS|PUT|PATCH|DELETE)$/; + push @{ $item_actions }, $m; + } + } + if($full_item_mod->can('resource_name')) { + my @operations = (); + my $op_config = {}; + my $resource_name = $full_item_mod->resource_name; + $journal_resource_config = NGCP::Panel::Utils::Journal::get_journal_resource_config($c->config,$resource_name); + if (exists $full_mod->config->{action}->{POST}) { + $op_config = NGCP::Panel::Utils::Journal::get_api_journal_op_config($c->config,$resource_name,NGCP::Panel::Utils::Journal::CREATE_JOURNAL_OP); + if ($op_config->{operation_enabled}) { + push(@operations,"create (POST $uri)"); + } + } + my $item_action_config = $full_item_mod->config->{action}; + if (exists $item_action_config->{PUT} || exists $item_action_config->{PATCH}) { + $op_config = NGCP::Panel::Utils::Journal::get_api_journal_op_config($c->config,$resource_name,NGCP::Panel::Utils::Journal::UPDATE_JOURNAL_OP); + if ($op_config->{operation_enabled}) { + if (exists $item_action_config->{PUT} && exists $item_action_config->{PATCH}) { + push(@operations,"update (PUT/PATCH $uri"."id)"); + } elsif (exists $item_action_config->{PUT}) { + push(@operations,"update (PUT $uri"."id)"); + } elsif (exists $item_action_config->{PATCH}) { + push(@operations,"update (PATCH $uri"."id)"); + } + } + } + if (exists $item_action_config->{DELETE}) { + $op_config = NGCP::Panel::Utils::Journal::get_api_journal_op_config($c->config,$resource_name,NGCP::Panel::Utils::Journal::CREATE_JOURNAL_OP); + if ($op_config->{operation_enabled}) { + push(@operations,"delete (DELETE $uri"."id)"); + } + } + $journal_resource_config->{operations} = \@operations; + $journal_resource_config->{format} = $op_config->{format}; + $journal_resource_config->{uri} = 'api/' . $resource_name . '/id/' . NGCP::Panel::Utils::Journal::API_JOURNAL_RESOURCE_NAME . '/'; + $journal_resource_config->{query_params} = ($full_item_mod->can('journal_query_params') ? $full_item_mod->journal_query_params : []); + $journal_resource_config->{sorting_cols} = NGCP::Panel::Utils::Journal::JOURNAL_FIELDS; + $journal_resource_config->{item_uri} = $journal_resource_config->{uri} . 'journalitemid'; + if (length(NGCP::Panel::Utils::Journal::API_JOURNALITEMTOP_RESOURCE_NAME) > 0) { + $journal_resource_config->{recent_uri} = $journal_resource_config->{uri} . NGCP::Panel::Utils::Journal::API_JOURNALITEMTOP_RESOURCE_NAME; + } } } @@ -110,12 +157,13 @@ sub GET : Allow { actions => $actions, item_actions => $item_actions, sorting_cols => $sorting_cols, - uri => "/api/$rel/", + uri => $uri, sample => $full_mod->can('documentation_sample') # generate pretty json, but without outer brackets (this is tricky though) ? to_json($full_mod->documentation_sample, {pretty => 1}) =~ s/(^\s*{\s*)|(\s*}\s*$)//rg =~ s/\n /\n/rg : undef, + journal_resource_config => $journal_resource_config, }; - + } $c->stash(template => 'api/root.tt'); diff --git a/lib/NGCP/Panel/Role/API/CFTimeSets.pm b/lib/NGCP/Panel/Role/API/CFTimeSets.pm index dae66c17f1..3250375e2f 100644 --- a/lib/NGCP/Panel/Role/API/CFTimeSets.pm +++ b/lib/NGCP/Panel/Role/API/CFTimeSets.pm @@ -49,6 +49,7 @@ sub hal_from_item { Data::HAL::Link->new(relation => 'self', href => sprintf("%s%d", $self->dispatch_path, $item->id)), Data::HAL::Link->new(relation => "ngcp:$type", href => sprintf("/api/%s/%d", $type, $item->id)), Data::HAL::Link->new(relation => "ngcp:subscribers", href => sprintf("/api/subscribers/%d", $b_subs_id)), + $self->get_journal_relation_link($item->id), ], relation => 'ngcp:'.$self->resource_name, ); diff --git a/lib/NGCP/Panel/Utils/Journal.pm b/lib/NGCP/Panel/Utils/Journal.pm index 842e60e2fe..04b6fc0c54 100644 --- a/lib/NGCP/Panel/Utils/Journal.pm +++ b/lib/NGCP/Panel/Utils/Journal.pm @@ -40,6 +40,8 @@ use constant CONTENT_DEFAULT_FORMAT => CONTENT_JSON_FORMAT; use constant API_JOURNAL_RELATION => 'ngcp:'.API_JOURNAL_RESOURCE_NAME; #use constant API_JOURNALITEM_RELATION => 'ngcp:journalitem'; +use constant JOURNAL_FIELDS => ['id', 'operation', 'resource_name', 'resource_id', 'timestamp', 'username']; + sub add_journal_item_hal { my ($controller,$c,$operation,@args) = @_; my $cfg = _get_api_journal_op_config($c,$controller->resource_name,$operation); @@ -357,7 +359,7 @@ sub get_journal_rs { 'resource_name' => $controller->resource_name, 'resource_id' => $resource_id, },($all_columns ? undef : { - columns => [qw(id operation resource_name resource_id timestamp username)], + columns => JOURNAL_FIELDS, #alias => 'me', })); diff --git a/share/templates/api/root/collection.tt b/share/templates/api/root/collection.tt index 5b24d14889..420b4f8031 100644 --- a/share/templates/api/root/collection.tt +++ b/share/templates/api/root/collection.tt @@ -418,4 +418,31 @@ Preference-Applied: return=minimal'; [% END -%] + +[% IF col.journal_resource_config.journal_resource_enabled -%] +Journal +A collection showing the history of modifications to a particular [% title %] collection item can be accessed using OPTIONS/GET/HEAD [% col.journal_resource_config.uri %]. By configuration, CRUD operations below will be recorded: +[% UNLESS col.journal_resource_config.operations.size -%] +
None. +[% ELSE -%] + +[% END -%] +Query parameters: + +The item's state after a completed operation is serialized and stored in [% col.journal_resource_config.format -%] format. It can be retrieved by requesting the corresponding journal record with OPTIONS/GET/HEAD [% col.journal_resource_config.item_uri %]. +[% IF col.journal_resource_config.recent_uri -%] +The most recent journal record is directly accessible using OPTIONS/GET/HEAD [% col.journal_resource_config.recent_uri %]. +[% END -%] +[% END -%] + [% # vim: set tabstop=4 syntax=html expandtab: -%] diff --git a/t/api-journals.t b/t/api-journals.t index 5a1bce71d1..015750da70 100644 --- a/t/api-journals.t +++ b/t/api-journals.t @@ -22,8 +22,8 @@ for my $path(qw#/etc/ngcp-panel/ngcp_panel.conf etc/ngcp_panel.conf ngcp_panel.c } } $panel_config //= 'ngcp_panel.conf'; -#my $catalyst_config = Config::General->new("../ngcp_panel.conf"); -my $catalyst_config = Config::General->new($panel_config); +my $catalyst_config = Config::General->new("../ngcp_panel.conf"); +#my $catalyst_config = Config::General->new($panel_config); my %config = $catalyst_config->getall(); my $enable_journal_tests = 1; @@ -38,17 +38,17 @@ my $ssl_ca_cert = $ENV{API_SSL_CA_CERT} || "/etc/ngcp-panel/api_ssl/api_ca.crt"; my ($ua, $req, $res); $ua = LWP::UserAgent->new; -$ua->ssl_opts( - SSL_cert_file => $valid_ssl_client_cert, - SSL_key_file => $valid_ssl_client_key, - SSL_ca_file => $ssl_ca_cert, -); - #$ua->ssl_opts( -# verify_hostname => 0, +# SSL_cert_file => $valid_ssl_client_cert, +# SSL_key_file => $valid_ssl_client_key, +# SSL_ca_file => $ssl_ca_cert, #); -#$ua->credentials("127.0.0.1:4443", "api_admin_http", 'administrator', 'administrator'); -##$ua->timeout(500); #useless, need to change the nginx timeout + +$ua->ssl_opts( + verify_hostname => 0, +); +$ua->credentials("127.0.0.1:4443", "api_admin_http", 'administrator', 'administrator'); +#$ua->timeout(500); #useless, need to change the nginx timeout my $t = time; my $default_reseller_id = 1; @@ -62,16 +62,16 @@ my $customercontact = test_customercontact($t,$reseller); my $customer = test_customer($customercontact,$billingprofile); my $customerpreferences = test_customerpreferences($customer); -my $subscriberprofileset = test_subscriberprofileset($t,$reseller); -my $subscriberprofile = test_subscriberprofile($t,$subscriberprofileset); -my $profilepreferences = test_profilepreferences($subscriberprofile); +#my $subscriberprofileset = test_subscriberprofileset($t,$reseller); +#my $subscriberprofile = test_subscriberprofile($t,$subscriberprofileset); +#my $profilepreferences = test_profilepreferences($subscriberprofile); my $subscriber = test_subscriber($t,$customer,$domain); my $cfdestinationset = test_cfdestinationset($t,$subscriber); -my $systemsoundset = test_soundset($t,$reseller); -my $customersoundset = test_soundset($t,$reseller,$customer); -my $subscriberpreferences = test_subscriberpreferences($subscriber,$customersoundset,$systemsoundset); +#my $systemsoundset = test_soundset($t,$reseller); +#my $customersoundset = test_soundset($t,$reseller,$customer); +#my $subscriberpreferences = test_subscriberpreferences($subscriber,$customersoundset,$systemsoundset);