From 996fd6000c57f3a96a64f2660c437761672ccc27 Mon Sep 17 00:00:00 2001 From: Andreas Granig Date: Thu, 31 Jul 2014 22:51:15 +0200 Subject: [PATCH] MT#8299 API: align PUT for pbxdevicemodels. Use multipart/form-data as we do in POST. --- .../Panel/Controller/API/PbxDeviceModels.pm | 36 ++++++++++- .../Controller/API/PbxDeviceModelsItem.pm | 21 +++--- lib/NGCP/Panel/Controller/Device.pm | 5 +- lib/NGCP/Panel/Form/Device/Model.pm | 11 ++++ lib/NGCP/Panel/Role/API/PbxDeviceModels.pm | 64 +++++++++++++++++-- 5 files changed, 121 insertions(+), 16 deletions(-) diff --git a/lib/NGCP/Panel/Controller/API/PbxDeviceModels.pm b/lib/NGCP/Panel/Controller/API/PbxDeviceModels.pm index 4386638150..51fd714644 100644 --- a/lib/NGCP/Panel/Controller/API/PbxDeviceModels.pm +++ b/lib/NGCP/Panel/Controller/API/PbxDeviceModels.pm @@ -15,13 +15,13 @@ require Catalyst::ActionRole::CheckTrailingSlash; require Catalyst::ActionRole::HTTPMethods; require Catalyst::ActionRole::RequireSSL; -# curl -v -X POST --user $USER --insecure -F front_image=@sandbox/spa504g-front.jpg -F mac_image=@sandbox/spa504g-back.jpg -F json='{"reseller_id":1, "vendor":"Cisco", "model":"SPA999", "linerange":[{"name": "Phone Keys", "num_lines":4, "can_private":true, "can_shared":true, "can_blf":true}]}' https://localhost:4443/api/pbxdevicemodels/ +# curl -v -X POST --user $USER --insecure -F front_image=@sandbox/spa504g-front.jpg -F mac_image=@sandbox/spa504g-back.jpg -F json='{"reseller_id":1, "vendor":"Cisco", "model":"SPA999", "linerange":[{"name": "Phone Keys", "can_private":true, "can_shared":true, "can_blf":true, "keys":[{"labelpos":"top", "x":5110, "y":5120},{"labelpos":"top", "x":5310, "y":5320}]}]}' https://localhost:4443/api/pbxdevicemodels/ class_has 'api_description' => ( is => 'ro', isa => 'Str', default => - 'Specifies a model to be set in PbxDeviceConfigs.', + 'Specifies a model to be set in PbxDeviceConfigs. Use a Content-Type "multipart/form-data", provide front_image and mac_image parts with the actual images, and an additional json part with the properties specified below, e.g.: curl -X POST --user $USER -F front_image=@/path/to/front.png -F mac_image=@/path/to/mac.png -F json=\'{"reseller_id":...}\' https://example.org:1443/api/pbxdevicemodels/', ); class_has 'query_params' => ( @@ -64,6 +64,38 @@ class_has 'query_params' => ( ]}, ); +class_has 'documentation_sample' => ( + is => 'ro', + default => sub { { + vendor => "testvendor", + model => "testmodel", + reseller_id => 1, + sync_method => "GET", + sync_params => '[% server.uri %]/$MA', + sync_uri => 'http://[% client.ip %]/admin/resync', + linerange => [ + { + name => "Phone Keys", + can_private => 1, + can_shared => 1, + can_blf => 1, + keys => [ + { + x => 100, + y => 200, + labelpos => "left", + }, + { + x => 100, + y => 300, + labelpos => "right", + }, + ], + }, + ], + } }, +); + with 'NGCP::Panel::Role::API::PbxDeviceModels'; diff --git a/lib/NGCP/Panel/Controller/API/PbxDeviceModelsItem.pm b/lib/NGCP/Panel/Controller/API/PbxDeviceModelsItem.pm index ca5983ec18..be2cfb7350 100644 --- a/lib/NGCP/Panel/Controller/API/PbxDeviceModelsItem.pm +++ b/lib/NGCP/Panel/Controller/API/PbxDeviceModelsItem.pm @@ -101,7 +101,7 @@ sub PATCH :Allow { my $item = $self->item_by_id($c, $id); last unless $self->resource_exists($c, pbxdevicemodel => $item); - my $old_resource = { $item->get_inflated_columns }; + my $old_resource = $self->resource_from_item($c, $item); my $resource = $self->apply_patch($c, $old_resource, $json); last unless $resource; @@ -137,13 +137,18 @@ sub PUT :Allow { my $item = $self->item_by_id($c, $id); last unless $self->resource_exists($c, pbxdevicemodel => $item); - my $resource = $self->get_valid_put_data( - c => $c, - id => $id, - media_type => 'application/json', - ); - last unless $resource; - my $old_resource = { $item->get_inflated_columns }; + + last unless $self->forbid_link_header($c); + last unless $self->valid_media_type($c, 'multipart/form-data'); + last unless $self->require_wellformed_json($c, 'application/json', $c->req->param('json')); + my $resource = JSON::from_json($c->req->param('json')); + $resource->{front_image} = $self->get_upload($c, 'front_image'); + last unless $resource->{front_image}; + # optional, don't set error + $resource->{mac_image} = $c->req->upload('mac_image'); + + + my $old_resource = $self->resource_from_item($c, $item); my $form = $self->get_form($c); $item = $self->update_item($c, $item, $old_resource, $resource, $form); diff --git a/lib/NGCP/Panel/Controller/Device.pm b/lib/NGCP/Panel/Controller/Device.pm index 7fedb8f0de..6836e14b1c 100644 --- a/lib/NGCP/Panel/Controller/Device.pm +++ b/lib/NGCP/Panel/Controller/Device.pm @@ -303,10 +303,11 @@ sub devmod_edit :Chained('devmod_base') :PathPart('edit') :Args(0) :Does(ACL) :A } $params->{reseller}{id} = delete $params->{reseller_id}; $params = $params->merge($c->session->{created_objects}); + $c->stash(edit_model => 1); # to make front_image optional if($c->user->is_superuser) { - $form = NGCP::Panel::Form::Device::ModelAdmin->new; + $form = NGCP::Panel::Form::Device::ModelAdmin->new(ctx => $c); } else { - $form = NGCP::Panel::Form::Device::Model->new; + $form = NGCP::Panel::Form::Device::Model->new(ctx => $c); } if($posted) { $c->req->params->{front_image} = $c->req->upload('front_image'); diff --git a/lib/NGCP/Panel/Form/Device/Model.pm b/lib/NGCP/Panel/Form/Device/Model.pm index 1168f32ae7..1c7b4f3dc2 100644 --- a/lib/NGCP/Panel/Form/Device/Model.pm +++ b/lib/NGCP/Panel/Form/Device/Model.pm @@ -252,5 +252,16 @@ has_block 'actions' => ( render_list => [qw/save/], ); +sub field_list { + my ($self) = @_; + + my $c = $self->ctx; + return unless($c); + + if($c->stash->{edit_model}) { + $self->field('front_image')->required(0); + } +} + 1; # vim: set tabstop=4 expandtab: diff --git a/lib/NGCP/Panel/Role/API/PbxDeviceModels.pm b/lib/NGCP/Panel/Role/API/PbxDeviceModels.pm index ff9afe07cc..4fe6729172 100644 --- a/lib/NGCP/Panel/Role/API/PbxDeviceModels.pm +++ b/lib/NGCP/Panel/Role/API/PbxDeviceModels.pm @@ -13,11 +13,12 @@ use Data::HAL::Link qw(); use HTTP::Status qw(:constants); use JSON qw(); use File::Type; +use Data::Dumper; use NGCP::Panel::Form::Device::ModelAPI; sub get_form { my ($self, $c) = @_; - return NGCP::Panel::Form::Device::ModelAPI->new($c); + return NGCP::Panel::Form::Device::ModelAPI->new(ctx => $c); } sub hal_from_item { @@ -66,14 +67,21 @@ sub resource_from_item { $resource{linerange} = []; foreach my $range($item->autoprov_device_line_ranges->all) { my $r = { $range->get_inflated_columns }; - foreach my $f(qw/device_id/) { + foreach my $f(qw/device_id num_lines/) { delete $r->{$f}; } $r->{id} = int($r->{id}); - $r->{num_lines} = int($r->{num_lines}); foreach my $f(qw/can_private can_shared can_blf/) { $r->{$f} = $r->{$f} ? JSON::true : JSON::false; } + $r->{keys} = []; + foreach my $key($range->annotations->all) { + push @{ $r->{keys} }, { + x => int($key->x), + y => int($key->y), + labelpos => $key->position, + }; + } push @{ $resource{linerange} }, $r; } @@ -134,12 +142,45 @@ sub update_item { return; } + + my $ft = File::Type->new(); + if($resource->{front_image}) { + my $front_image = delete $resource->{front_image}; + $resource->{front_image} = $front_image->slurp; + $resource->{front_image_type} = $ft->mime_type($resource->{front_image}); + } + if($resource->{mac_image}) { + my $front_image = delete $resource->{mac_image}; + $resource->{mac_image} = $front_image->slurp; + $resource->{mac_image_type} = $ft->mime_type($resource->{mac_image}); + } + $item->update($resource); my @existing_range = (); my $range_rs = $item->autoprov_device_line_ranges; foreach my $range(@{ $linerange }) { - next unless(defined $range); + + unless(ref $range eq "HASH") { + $c->log->error("all elements in linerange must be hashes, but this is " . ref $range . ": " . Dumper $range); + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid range definition inside linerange parameter, all must be hash"); + return; + } + foreach my $elem(qw/can_private can_shared can_blf keys/) { + unless(exists $range->{$elem}) { + $c->log->error("missing mandatory attribute '$elem' in a linerange element"); + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid range definition inside linerange parameter, missing attribute '$elem'"); + return; + } + } + unless(ref $range->{keys} eq "ARRAY") { + $c->log->error("linerange.keys must be array"); + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid linerange.keys parameter, must be array"); + last; + } + $range->{num_lines} = @{ $range->{keys} }; # backward compatibility + my $keys = delete $range->{keys}; + my $old_range; if(defined $range->{id}) { # should be an existing range, do update @@ -158,6 +199,21 @@ sub update_item { # new range $old_range = $range_rs->create($range); } + + $old_range->annotations->delete; + my $i = 0; + foreach my $label(@{ $keys }) { + unless(ref $label eq "HASH") { + $c->log->error("all elements in linerange must be hashes, but this is " . ref $range . ": " . Dumper $range); + $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid range definition inside linerange parameter, all must be hash"); + return; + } + + $label->{line_index} = $i++; + $label->{position} = delete $label->{labelpos}; + $old_range->annotations->create($label); + } + push @existing_range, $old_range->id; # mark as valid (delete others later) # delete field device line assignments with are out-of-range or use a