MT#59478 adapt /api/cftimesets to global transactions, more fixes

* /api/cftimesets now fully use Entities/EntitiesItem
* EntitiesItem: delete(): fix delete_item() expression processing
* Entities/EntitiesItem: post/put/patch/delete: go to TX_END
  in scenarios where after a method call (e.g. update_item()) there
  is a normal return from the function but errors in $c->error, so
  that they are also caught correctly

Change-Id: I3bef409ded590796c2bba4f30acd28b02e99065b
mr12.4
Kirill Solomko 1 year ago
parent 91f1dcf37e
commit 9e3653341a

@ -61,124 +61,56 @@ __PACKAGE__->set_config({
allowed_roles => [qw/admin reseller ccareadmin ccare subscriberadmin subscriber/],
});
sub GET :Allow {
my ($self, $c) = @_;
my $page = $c->request->params->{page} // 1;
my $rows = $c->request->params->{rows} // 10;
{
my $timesets = $self->item_rs($c);
(my $total_count, $timesets, my $timesets_rows) = $self->paginate_order_collection($c, $timesets);
my (@embedded, @links);
for my $tset (@$timesets_rows) {
push @embedded, $self->hal_from_item($c, $tset, "cftimesets");
push @links, Data::HAL::Link->new(
relation => 'ngcp:'.$self->resource_name,
href => sprintf('%s%d', $self->dispatch_path, $tset->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/'),
$self->collection_nav_links($c, $page, $rows, $total_count, $c->request->path, $c->request->query_params);
my $hal = Data::HAL->new(
embedded => [@embedded],
links => [@links],
);
$hal->resource({
total_count => $total_count,
sub create_item {
my ($self, $c, $resource, $form, $process_extras) = @_;
my $schema = $c->model('DB');
my $tset;
if($c->user->roles eq "subscriberadmin" || $c->user->roles eq "subscriber") {
$resource->{subscriber_id} = $c->user->voip_subscriber->id;
} elsif(!defined $resource->{subscriber_id}) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Missing mandatory field 'subscriber_id'");
return;
}
my $b_subscriber = $schema->resultset('voip_subscribers')->find({
id => $resource->{subscriber_id},
});
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);
unless($b_subscriber) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'subscriber_id'.");
return;
}
return;
}
sub POST :Allow {
my ($self, $c) = @_;
my $guard = $c->model('DB')->txn_scope_guard;
{
my $schema = $c->model('DB');
my $resource = $self->get_valid_post_data(
c => $c,
media_type => 'application/json',
);
last unless $resource;
my $form = $self->get_form($c);
last unless $self->validate_form(
c => $c,
resource => $resource,
form => $form,
);
my $tset;
if($c->user->roles eq "subscriberadmin" || $c->user->roles eq "subscriber") {
$resource->{subscriber_id} = $c->user->voip_subscriber->id;
} elsif(!defined $resource->{subscriber_id}) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Missing mandatory field 'subscriber_id'");
last;
}
my $b_subscriber = $schema->resultset('voip_subscribers')->find({
id => $resource->{subscriber_id},
my $subscriber = $b_subscriber->provisioning_voip_subscriber;
unless($subscriber) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid subscriber.");
return;
}
if (! exists $resource->{times} ) {
$resource->{times} = [];
}
if (ref $resource->{times} ne "ARRAY") {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid field 'times'. Must be an array.");
return;
}
my $times = $resource->{times};
# enable tz and use_owner_tz params for POST:
#$times = $self->apply_owner_timezone($c,$b_subscriber,$resource->{times},'deflate');
try {
$tset = $schema->resultset('voip_cf_time_sets')->create({
name => $resource->{name},
subscriber_id => $subscriber->id,
});
unless($b_subscriber) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'subscriber_id'.");
last;
}
my $subscriber = $b_subscriber->provisioning_voip_subscriber;
unless($subscriber) {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid subscriber.");
last;
}
if (! exists $resource->{times} ) {
$resource->{times} = [];
}
if (ref $resource->{times} ne "ARRAY") {
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid field 'times'. Must be an array.");
last;
}
my $times = $resource->{times};
# enable tz and use_owner_tz params for POST:
#$times = $self->apply_owner_timezone($c,$b_subscriber,$resource->{times},'deflate');
try {
$tset = $schema->resultset('voip_cf_time_sets')->create({
name => $resource->{name},
subscriber_id => $subscriber->id,
});
for my $t ( @$times ) {
delete $t->{time_set_id};
$tset->create_related("voip_cf_periods", $t);
}
} catch($e) {
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create cftimeset.", $e);
last;
for my $t ( @$times ) {
delete $t->{time_set_id};
$tset->create_related("voip_cf_periods", $t);
}
last unless $self->add_create_journal_item_hal($c,sub {
my $self = shift;
my ($c) = @_;
my $_dset = $self->item_by_id($c, $tset->id);
return $self->hal_from_item($c, $_dset, "cftimesets"); });
$guard->commit;
$c->response->status(HTTP_CREATED);
$c->response->header(Location => sprintf('/%s%d', $c->request->path, $tset->id));
$c->response->body(q());
} catch($e) {
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create cftimeset.", $e);
return;
}
return;
return $tset;
}
1;

@ -41,124 +41,25 @@ __PACKAGE__->set_config({
allowed_roles => {
Default => [qw/admin reseller ccareadmin ccare subscriberadmin subscriber/],
Journal => [qw/admin reseller ccareadmin ccare/],
}
},
PATCH => { ops => [qw/add replace remove copy/] },
});
sub GET :Allow {
my ($self, $c, $id) = @_;
{
last unless $self->valid_id($c, $id);
my $tset = $self->item_by_id($c, $id);
last unless $self->resource_exists($c, timeset => $tset);
my $hal = $self->hal_from_item($c, $tset, "cftimesets");
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 delete_item {
my ($self, $c, $item) = @_;
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',
ops => [qw/add replace remove copy/],
);
last unless $json;
my $tset = $self->item_by_id($c, $id);
last unless $self->resource_exists($c, timeset => $tset);
my $old_resource = $self->hal_from_item($c, $tset, "cftimesets")->resource;
my $resource = $self->apply_patch($c, $old_resource, $json);
last unless $resource;
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, "cftimesets");
last unless $self->add_update_journal_item_hal($c,$hal);
$guard->commit;
$self->return_representation($c, 'hal' => $hal, 'preference' => $preference );
}
return;
}
return unless $self->check_subscriber_can_update_item($c, $item);
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 $tset = $self->item_by_id($c, $id);
last unless $self->resource_exists($c, timeset => $tset);
my $resource = $self->get_valid_put_data(
c => $c,
id => $id,
media_type => 'application/json',
);
last unless $resource;
my $old_resource = undef; #unused: { $tset->get_inflated_columns };
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, "cftimesets");
last unless $self->add_update_journal_item_hal($c,$hal);
$guard->commit;
$self->return_representation($c, 'hal' => $hal, 'preference' => $preference );
try {
$item->delete;
} catch($e) {
my $id = $item->id;
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error",
"Failed to delete cftimeset with id '$id'", $e);
return;
}
return;
}
sub DELETE :Allow {
my ($self, $c, $id) = @_;
my $guard = $c->model('DB')->txn_scope_guard;
{
my $tset = $self->item_by_id($c, $id);
last unless $self->resource_exists($c, timeset => $tset);
return unless $self->check_subscriber_can_update_item($c, $tset);
last unless $self->add_delete_journal_item_hal($c,sub {
my $self = shift;
my ($c) = @_;
return $self->hal_from_item($c, $tset, "cftimesets"); });
try {
$tset->delete;
} catch($e) {
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Internal Server Error",
"Failed to delete cftimeset with id '$id'", $e);
last;
}
$guard->commit;
$c->response->status(HTTP_NO_CONTENT);
$c->response->body(q());
}
return;
return 1;
}
sub get_journal_methods{

@ -300,8 +300,9 @@ sub _merge_adjacent {
}
sub hal_from_item {
my ($self, $c, $item, $type) = @_;
my $form;
my ($self, $c, $item, $form, $params) = @_;
my $type = 'cftimesets';
my %resource = $item->get_inflated_columns;
my @times;
@ -400,6 +401,22 @@ sub check_subscriber_can_update_item {
return 1;
}
sub resource_from_item {
my ($self, $c, $item) = @_;
my $resource = { $item->get_inflated_columns };
$resource->{subscriber_id} = $item->subscriber->voip_subscriber->id;
$resource->{times} //= [];
foreach my $period ($item->voip_cf_periods->all) {
push @{$resource->{times}}, { $period->get_inflated_columns };
}
return $resource;
}
sub update_item {
my ($self, $c, $item, $old_resource, $resource, $form) = @_;

@ -294,6 +294,9 @@ sub patch {
#$old_resource = clone($old_resource);
##without it error: The entity could not be processed: Modification of a read-only value attempted at /usr/share/perl5/JSON/Pointer.pm line 200, <$fh> line 1.\n
$resource = $self->apply_patch($c, $old_resource, $json);
if ($c->has_errors) {
goto TX_END;
}
unless ($resource) {
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, 'Internal Server Error',
'#apply_patch') unless $c->has_errors;
@ -301,6 +304,9 @@ sub patch {
}
($item, $form, $process_extras) = $self->update_item($c, $item, $old_resource, $resource, $form, $process_extras );
if ($c->has_errors) {
goto TX_END;
}
unless ($item) {
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, 'Internal Server Error',
'#update_item') unless $c->has_errors;
@ -392,6 +398,9 @@ sub put {
my ($data_processed_result);
if (!$non_json_data || !$data) {
($item, $form, $process_extras) = $self->update_item($c, $item, $old_resource, $resource, $form, $process_extras );
if ($c->has_errors) {
goto TX_END;
}
unless ($item) {
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, 'Internal Server Error',
'#update_item') unless $c->has_errors;
@ -469,13 +478,16 @@ sub delete { ## no critic (ProhibitBuiltinHomonyms)
}
($hal, $hal_id) = $self->get_journal_item_hal($c, $item);
unless ($self->delete_item($c, $item)) {
if ($self->delete_item($c, $item)) {
unless ($self->add_journal_item_hal($c, { hal => $hal, ($hal_id ? ( id => $hal_id, ) : ()) })) {
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, 'Internal Server Error',
'#add_journal_item_hal');
goto TX_END;
}
} else {
if ($c->has_errors) {
goto TX_END;
}
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, 'Internal Server Error',
'non 1 return from $self->delete_item()');
goto TX_END;

Loading…
Cancel
Save