From 829b432b47ccd1e9b3b22ceff55ce0d346d25cdf Mon Sep 17 00:00:00 2001 From: Andreas Granig Date: Fri, 6 Dec 2013 17:04:32 +0100 Subject: [PATCH] MT#5299 API: PUT/PATCH/GET billingprofiles item --- .../Controller/API/BillingProfilesItem.pm | 177 ++++++++++++++++++ .../Panel/Controller/API/ContractsItem.pm | 4 +- lib/NGCP/Panel/Role/API/BillingProfiles.pm | 84 +++------ 3 files changed, 202 insertions(+), 63 deletions(-) create mode 100644 lib/NGCP/Panel/Controller/API/BillingProfilesItem.pm diff --git a/lib/NGCP/Panel/Controller/API/BillingProfilesItem.pm b/lib/NGCP/Panel/Controller/API/BillingProfilesItem.pm new file mode 100644 index 0000000000..ce057c30b5 --- /dev/null +++ b/lib/NGCP/Panel/Controller/API/BillingProfilesItem.pm @@ -0,0 +1,177 @@ +package NGCP::Panel::Controller::API::BillingProfilesItem; +use Sipwise::Base; +use namespace::sweep; +use HTTP::Headers qw(); +use HTTP::Status qw(:constants); +use MooseX::ClassAttribute qw(class_has); +use NGCP::Panel::Form::BillingProfile::Admin qw(); +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'; +with 'NGCP::Panel::Role::API::BillingProfiles'; + +class_has('resource_name', is => 'ro', default => 'billingprofiles'); +class_has('dispatch_path', is => 'ro', default => '/api/billingprofiles/'); +class_has('relation', is => 'ro', default => 'http://purl.org/sipwise/ngcp-api/#rel-billingprofiles'); + +__PACKAGE__->config( + action => { + map { $_ => { + ACLDetachTo => '/api/root/invalid_user', + AllowedRole => 'api_admin', + 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 $profile = $self->profile_by_id($c, $id); + last unless $self->resource_exists($c, billingprofile => $profile); + + my $hal = $self->hal_from_profile($c, $profile); + + # TODO: we don't need reseller stuff here! + 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 $profile = $self->profile_by_id($c, $id); + last unless $self->resource_exists($c, billingprofile => $profile); + my $old_resource = { $profile->get_inflated_columns }; + my $resource = $self->apply_patch($c, $old_resource, $json); + last unless $resource; + + my $form = NGCP::Panel::Form::BillingProfile::Admin->new; + last unless $self->update_profile($c, $profile, $old_resource, $resource, $form); + + $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_profile($c, $profile, $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 $profile = $self->profile_by_id($c, $id); + last unless $self->resource_exists($c, billingprofile => $profile ); + my $resource = $self->get_valid_put_data( + c => $c, + id => $id, + media_type => 'application/json', + ); + last unless $resource; + my $old_resource = { $profile->get_inflated_columns }; + + my $form = NGCP::Panel::Form::BillingProfile::Admin->new; + last unless $self->update_profile($c, $profile, $old_resource, $resource, $form); + + $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_profile($c, $profile, $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; +} + +# we don't allow to DELETE a billing profile + +sub end : Private { + my ($self, $c) = @_; + + $self->log_response($c); +} + +# vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Controller/API/ContractsItem.pm b/lib/NGCP/Panel/Controller/API/ContractsItem.pm index 9dfecceae8..f06151ffbd 100644 --- a/lib/NGCP/Panel/Controller/API/ContractsItem.pm +++ b/lib/NGCP/Panel/Controller/API/ContractsItem.pm @@ -104,12 +104,12 @@ sub PATCH :Allow { my $contract = $self->contract_by_id($c, $id); last unless $self->resource_exists($c, contract => $contract); my $old_resource = { $contract->get_inflated_columns }; - # make sure we don't modify $old_resource, but use a copy: - my $resource = $self->apply_patch($c, { %{ $old_resource } }, $json); + my $resource = $self->apply_patch($c, $old_resource, $json); last unless $resource; my $form = NGCP::Panel::Form::Contract::PeeringReseller->new; last unless $self->update_contract($c, $contract, $old_resource, $resource, $form); + $guard->commit; if ('minimal' eq $preference) { diff --git a/lib/NGCP/Panel/Role/API/BillingProfiles.pm b/lib/NGCP/Panel/Role/API/BillingProfiles.pm index b2b7e28786..fd21d607a8 100644 --- a/lib/NGCP/Panel/Role/API/BillingProfiles.pm +++ b/lib/NGCP/Panel/Role/API/BillingProfiles.pm @@ -26,7 +26,6 @@ sub hal_from_profile { ), 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/'), - # TODO: if called from collection, this is wrong, as the id is missing when we put it into embedded! Same for systemcontacts/contracts: Data::HAL::Link->new(relation => 'self', href => sprintf("%s%d", $self->dispatch_path, $profile->id)), map { Data::HAL::Link->new(relation => 'ngcp:billingfees', href => sprintf("/api/billingfees/%d", $_->id)) } $profile->billing_fees->all, ], @@ -46,83 +45,46 @@ sub hal_from_profile { return $hal; } -sub contract_by_id { +sub profile_by_id { my ($self, $c, $id) = @_; - # we only return system contracts, that is, those with contacts without - # reseller - my $contracts = NGCP::Panel::Utils::Contract::get_contract_rs( - schema => $c->model('DB'), - ); - $contracts = $contracts->search({ - 'contact.reseller_id' => undef - },{ - join => 'contact', - '+select' => 'billing_mappings.id', - '+as' => 'bmid', - }); + my $profiles = $c->model('DB')->resultset('billing_profiles'); + if($c->user->roles eq "api_admin") { + } elsif($c->user->roles eq "api_reseller") { + $profiles = $profiles->search({ + reseller_id => $c->user->reseller_id, + }); + } else { + $profiles = $profiles->search({ + reseller_id => $c->user->contract->contact->reseller_id, + }); + } - return $contracts->find($id); + return $profiles->find($id); } -sub update_contract { - my ($self, $c, $contract, $old_resource, $resource, $form) = @_; +sub update_profile { + my ($self, $c, $profile, $old_resource, $resource, $form) = @_; - my $billing_mapping = $contract->billing_mappings->find($contract->get_column('bmid')); - $old_resource->{billing_profile_id} = $billing_mapping->billing_profile_id; - $resource->{billing_profile_id} //= $old_resource->{billing_profile_id}; - - $form //= NGCP::Panel::Form::Contract::PeeringReseller->new; + $form //= NGCP::Panel::Form::BillingProfile::Admin->new; return unless $self->validate_form( c => $c, form => $form, resource => $resource, ); - my $now = NGCP::Panel::Utils::DateTime::current_local; - $resource->{modify_timestamp} = $now; - - if($old_resource->{billing_profile_id} != $resource->{billing_profile_id}) { - my $billing_profile = $c->model('DB')->resultset('billing_profiles')->find($resource->{billing_profile_id}); - unless($billing_profile) { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'billing_profile_id'"); - return; - } - $contract->billing_mappings->create({ - start_date => NGCP::Panel::Utils::DateTime::current_local, - billing_profile_id => $resource->{billing_profile_id}, - product_id => $billing_mapping->product_id, - }); - } - delete $resource->{billing_profile_id}; - - - if($old_resource->{contact_id} != $resource->{contact_id}) { - my $syscontact = $c->model('DB')->resultset('contacts') - ->search({ reseller_id => undef }) - ->find($resource->{contact_id}); - unless($syscontact) { - $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'contact_id'"); - return; - } - } - - $contract->update($resource); - - if($old_resource->{status} ne $resource->{status}) { - if($contract->id == 1) { - $self->error($c, HTTP_FORBIDDEN, "Cannot set contract status to '".$resource->{status}."' for contract id '1'"); + if($old_resource->{reseller_id} != $resource->{reseller_id}) { + my $reseller = $c->model('DB')->resultset('resellers') + ->find($resource->{reseller_id}); + unless($reseller) { + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'reseller_id'"); return; } - NGCP::Panel::Utils::Contract::recursively_lock_contract( - c => $c, - contract => $contract, - ); } - # TODO: what about changed product, do we allow it? + $profile->update($resource); - return $contract; + return $profile; } 1;