You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
440 lines
15 KiB
440 lines
15 KiB
package NGCP::Panel::Role::API::CFTimeSets;
|
|
use NGCP::Panel::Utils::Generic qw(:all);
|
|
|
|
use Sipwise::Base;
|
|
|
|
use parent 'NGCP::Panel::Role::API';
|
|
|
|
|
|
use boolean qw(true);
|
|
use Data::HAL qw();
|
|
use Data::HAL::Link qw();
|
|
use HTTP::Status qw(:constants);
|
|
use JSON::Types;
|
|
use NGCP::Panel::Utils::Subscriber;
|
|
|
|
use NGCP::Panel::Form;
|
|
|
|
use NGCP::Panel::Utils::DateTime qw();
|
|
|
|
sub get_form {
|
|
my ($self, $c) = @_;
|
|
if($c->user->roles eq "subscriber" || $c->user->roles eq "subscriberadmin") {
|
|
return NGCP::Panel::Form::get("NGCP::Panel::Form::CallForward::CFTimeSetSubAPI", $c);
|
|
} else {
|
|
return NGCP::Panel::Form::get("NGCP::Panel::Form::CallForward::CFTimeSetAPI", $c);
|
|
}
|
|
}
|
|
|
|
sub apply_owner_timezone {
|
|
|
|
my ($self, $c, $subscriber, $times, $mode) = @_;
|
|
|
|
my $tz_name;
|
|
if($c->req->param('tz')) {
|
|
if (DateTime::TimeZone->is_valid_name($c->req->param('tz'))) {
|
|
$tz_name = $c->req->param('tz');
|
|
} else {
|
|
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Query parameter 'tz' value is not a valid time zone");
|
|
return;
|
|
}
|
|
} elsif ($subscriber and $c->req->param('use_owner_tz')) {
|
|
my $tz = $c->model('DB')->resultset('voip_subscriber_timezone')->search_rs({
|
|
subscriber_id => $subscriber->id
|
|
})->first;
|
|
$tz_name = NGCP::Panel::Utils::DateTime::normalize_db_tz_name($tz->name) if $tz;
|
|
}
|
|
$times //= [];
|
|
my $tz_local = DateTime::TimeZone->new(name => 'local');
|
|
my ($tz,$offset);
|
|
if ($tz_name
|
|
and ($tz = DateTime::TimeZone->new(name => $tz_name))
|
|
and abs($offset = $tz->offset_for_datetime(DateTime->now()) - $tz_local->offset_for_datetime(DateTime->now())) > 0) {
|
|
|
|
my $offset_hrs = int($offset / 3600.0);
|
|
if ($offset_hrs * 3600 != $offset) {
|
|
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "only full hours supported for timezone offset ($tz_name: $offset seconds = $offset_hrs hours)");
|
|
return;
|
|
}
|
|
|
|
#foreach my $time (@$times) {
|
|
# foreach $field (qw(year month mday) {
|
|
# if (exists $time->{$field} and $time->{$field}) {
|
|
# $self->error($c, HTTP_UNPROCESSABLE_ENTITY, "cannot apply a timezone offset for time with '$field' field ($time->{$field})");
|
|
# return;
|
|
# }
|
|
# }
|
|
#}
|
|
|
|
my $merge = 0;
|
|
if ('deflate' eq $mode) { #for writing to db
|
|
$offset_hrs = $offset_hrs * -1;
|
|
$c->log->debug("cf timeset $mode for timezone $tz_name: offset $offset_hrs hours");
|
|
} elsif ('inflate' eq $mode) { #for reading from db
|
|
$merge = 1;
|
|
$c->log->debug("cf timeset $mode for timezone $tz_name: offset $offset_hrs hours");
|
|
} else {
|
|
die("invalid mode $mode");
|
|
}
|
|
|
|
my ($yearmonthmday_map,$yearmonthmdays) = array_to_map($times,sub { my $time = shift;
|
|
return (length($time->{year}) > 0 ? $time->{year} : '*') .
|
|
'_' . ($time->{month} || '*') . '_' . ($time->{mday} || '*');
|
|
},undef,'group');
|
|
|
|
$times = [];
|
|
foreach my $yearmonthmday (@$yearmonthmdays) {
|
|
if ($offset_hrs > 0) {
|
|
push(@$times,@{_add($yearmonthmday_map->{$yearmonthmday},$offset_hrs,$merge)});
|
|
} else {
|
|
push(@$times,@{_subtract($yearmonthmday_map->{$yearmonthmday},$offset_hrs,$merge)});
|
|
}
|
|
}
|
|
|
|
} else {
|
|
$c->log->debug("no timezone to convert to, or zero tz offset");
|
|
}
|
|
return $times;
|
|
|
|
}
|
|
|
|
sub _add {
|
|
my ($times,$offset_hrs,$merge) = @_;
|
|
|
|
my @result = ();
|
|
foreach my $time (@$times) {
|
|
my $p1 = { %$time };
|
|
unless (length($time->{hour}) > 0) {
|
|
#nothing to do if there is no hour defined:
|
|
push(@result,$p1);
|
|
next;
|
|
}
|
|
my ($hour_start,$hour_end) = split(/\-/, $time->{hour});
|
|
my $hour_range;
|
|
if (defined $hour_end) {
|
|
$hour_range = 1;
|
|
} else {
|
|
$hour_end = $hour_start;
|
|
$hour_range = 0;
|
|
}
|
|
$hour_start += abs($offset_hrs);
|
|
$hour_end += abs($offset_hrs);
|
|
if ($hour_start < 24 and $hour_end < 24) {
|
|
$p1->{hour} = ($hour_range ? $hour_start . '-' . $hour_end : $hour_start);
|
|
push(@result,$p1);
|
|
next;
|
|
}
|
|
my ($wday_start, $wday_end) = split(/\-/, $time->{wday} || '1-7');
|
|
my $wday_range;
|
|
if (defined $wday_end) {
|
|
$wday_range = 1;
|
|
} else {
|
|
$wday_end = $wday_start;
|
|
$wday_range = 0;
|
|
}
|
|
my @nums = ();
|
|
if ($wday_start <= $wday_end) {
|
|
push(@nums,$wday_start .. $wday_end);
|
|
} else {
|
|
push(@nums,$wday_start .. 7);
|
|
push(@nums,1 .. $wday_end);
|
|
}
|
|
my ($p2,$p_shift_wday);
|
|
if ($hour_start > 23 and $hour_end > 23) { #26-28
|
|
$p1->{hour} = ($hour_range ? ($hour_start % 24) . '-' . ($hour_end % 24) : ($hour_start % 24)); #2-4
|
|
$p_shift_wday = $p1;
|
|
} elsif ($hour_start < $hour_end) {
|
|
if ($hour_end > 23) { #17-23 +3-> 20-26
|
|
$p1->{hour} = $hour_start . '-23'; #20-0
|
|
$p2 = { %$time };
|
|
$p2->{hour} = '0-' . ($hour_end % 24); #0-2
|
|
$p_shift_wday = $p2;
|
|
}
|
|
} else {
|
|
if ($hour_start > 23) { #23-17 +3-> 26-20
|
|
$p1->{hour} = ($hour_start % 24) . '-' . $hour_end; #2-20
|
|
$p_shift_wday = $p1;
|
|
}
|
|
}
|
|
if ($p_shift_wday and (scalar @nums) < 7) {
|
|
$p_shift_wday->{wday} = ($wday_range ? (($wday_start) % 7 + 1) . '-' . (($wday_end) % 7 + 1) : (($wday_start) % 7 + 1));
|
|
}
|
|
push(@result,$p1);
|
|
push(@result,$p2) if $p2;
|
|
}
|
|
return ($merge ? merge_adjacent(\@result) : \@result);
|
|
|
|
}
|
|
|
|
sub _subtract {
|
|
my ($times,$offset_hrs,$merge) = @_;
|
|
|
|
my @result = ();
|
|
foreach my $time (@$times) {
|
|
my $p1 = { %$time };
|
|
unless (length($time->{hour}) > 0) {
|
|
#nothing to do if there is no hour defined:
|
|
push(@result,$p1);
|
|
next;
|
|
}
|
|
my ($hour_start,$hour_end) = split(/\-/, $time->{hour});
|
|
my $hour_range;
|
|
if (defined $hour_end) {
|
|
$hour_range = 1;
|
|
} else {
|
|
$hour_end = $hour_start;
|
|
$hour_range = 0;
|
|
}
|
|
$hour_start -= abs($offset_hrs);
|
|
$hour_end -= abs($offset_hrs);
|
|
if ($hour_start >= 0 and $hour_end >= 0) {
|
|
$p1->{hour} = ($hour_range ? $hour_start . '-' . $hour_end : $hour_start);
|
|
push(@result,$p1);
|
|
next;
|
|
}
|
|
my ($wday_start, $wday_end) = split(/\-/, $time->{wday} || '1-7');
|
|
my $wday_range;
|
|
if (defined $wday_end) {
|
|
$wday_range = 1;
|
|
} else {
|
|
$wday_end = $wday_start;
|
|
$wday_range = 0;
|
|
}
|
|
my @nums = ();
|
|
if ($wday_start <= $wday_end) {
|
|
push(@nums,$wday_start .. $wday_end);
|
|
} else {
|
|
push(@nums,$wday_start .. 7);
|
|
push(@nums,1 .. $wday_end);
|
|
}
|
|
my ($p2,$p_shift_wday);
|
|
if ($hour_start < 0 and $hour_end < 0) { #-4 - -2
|
|
$p1->{hour} = ($hour_range ? ($hour_start % 24) . '-' . ($hour_end % 24) : ($hour_start % 24)); #20-22
|
|
$p_shift_wday = $p1;
|
|
} elsif ($hour_start < $hour_end) { #-4 - 3
|
|
if ($hour_start < 0) { #0-7 -4-> -4 - 3
|
|
$p1->{hour} = ($hour_start % 24) . '-23'; #20-0
|
|
$p2 = { %$time };
|
|
$p2->{hour} = '0-' . $hour_end; #0-3
|
|
$p_shift_wday = $p1;
|
|
}
|
|
} else {
|
|
if ($hour_end < 0) { #22 - 2 -6-> 16 - -4
|
|
$p1->{hour} = $hour_start . '-' . ($hour_end % 24); #16-20
|
|
$p_shift_wday = $p1;
|
|
}
|
|
}
|
|
if ($p_shift_wday and (scalar @nums) < 7) {
|
|
$p_shift_wday->{wday} = ($wday_range ? (($wday_start - 2) % 7 + 1) . '-' . (($wday_end - 2) % 7 + 1) : (($wday_start - 2) % 7 + 1));
|
|
}
|
|
push(@result,$p1);
|
|
push(@result,$p2) if $p2;
|
|
}
|
|
return ($merge ? _merge_adjacent(\@result) : \@result);
|
|
|
|
}
|
|
|
|
sub _merge_adjacent {
|
|
my ($times) = @_;
|
|
|
|
my ($wday_map,$wdays) = array_to_map($times,sub { my $time = shift;
|
|
my $wday = $time->{wday} || '1-7';
|
|
$wday = '1-7' if $time->{wday} eq '7-1';
|
|
$wday .= '_' . (defined $time->{minute} ? $time->{minute} : '*');
|
|
return $wday;
|
|
},undef,'group');
|
|
|
|
my @result = ();
|
|
my $idx = 0;
|
|
foreach my $wday (@$wdays) {
|
|
my %hour_start_map = ();
|
|
my %hour_end_map = ();
|
|
my %skip_map = ();
|
|
my $old_idx = $idx;
|
|
foreach my $time (@{$wday_map->{$wday}}) {
|
|
if (length($time->{hour}) > 0) {
|
|
my ($hour_start,$hour_end) = split(/\-/, $time->{hour});
|
|
$hour_end //= $hour_start;
|
|
if ($hour_end >= $hour_start) { #we do not create any adjacent roll-over hours, so we also skip such when merging
|
|
if (not defined $hour_start_map{$hour_start}
|
|
or $hour_end > $hour_start_map{$hour_start}->{hour_end}) {
|
|
$hour_start_map{$hour_start} = { hour_end => $hour_end, idx => $idx, };
|
|
} else {
|
|
$skip_map{$idx} = 0;
|
|
}
|
|
if (not defined $hour_end_map{$hour_end}
|
|
or $hour_start < $hour_end_map{$hour_end}->{hour_start}) {
|
|
$hour_end_map{$hour_end} = { hour_start => $hour_start, }; #, idx => $idx,
|
|
} else {
|
|
$skip_map{$idx} = 0;
|
|
}
|
|
} else {
|
|
$skip_map{$idx} = 1;
|
|
}
|
|
} else {
|
|
$skip_map{$idx} = 1;
|
|
}
|
|
$idx++;
|
|
}
|
|
$idx = $old_idx;
|
|
foreach my $time (@{$wday_map->{$wday}}) {
|
|
my $p = { %$time };
|
|
if (exists $skip_map{$idx}) {
|
|
push(@result,$p) if $skip_map{$idx};
|
|
} else {
|
|
my ($hour_start,$hour_end) = split(/\-/, $time->{hour});
|
|
$hour_end //= $hour_start;
|
|
#if ($hour_end_map{$hour_end}->{idx} == $idx) {
|
|
my $adjacent_start = $hour_end + 1;
|
|
if (exists $hour_start_map{$adjacent_start}) {
|
|
$p->{hour} = $hour_start . '-' . $hour_start_map{$adjacent_start}->{hour_end};
|
|
$skip_map{$hour_start_map{$adjacent_start}->{idx}} = 0;
|
|
}
|
|
push(@result,$p);
|
|
#}
|
|
}
|
|
$idx++;
|
|
}
|
|
}
|
|
return \@result;
|
|
}
|
|
|
|
sub hal_from_item {
|
|
my ($self, $c, $item, $type) = @_;
|
|
my $form;
|
|
|
|
my %resource = $item->get_inflated_columns;
|
|
my @times;
|
|
for my $time ($item->voip_cf_periods->all) {
|
|
my $timeelem = {$time->get_inflated_columns};
|
|
delete $timeelem->{'id'};
|
|
push @times, $timeelem;
|
|
}
|
|
$resource{times} = \@times;
|
|
# enable tz and use_owner_tz params for GET:
|
|
#$resource{times} = $self->apply_owner_timezone($c,$item->subscriber->voip_subscriber,\@times,'inflate');
|
|
|
|
my $b_subs_id = $item->subscriber->voip_subscriber->id;
|
|
$resource{subscriber_id} = $b_subs_id;
|
|
|
|
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("%s", $self->dispatch_path)),
|
|
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:$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($c, $item->id),
|
|
],
|
|
relation => 'ngcp:'.$self->resource_name,
|
|
);
|
|
|
|
$form //= $self->get_form($c);
|
|
return unless $self->validate_form(
|
|
c => $c,
|
|
form => $form,
|
|
resource => \%resource,
|
|
run => 0,
|
|
);
|
|
$hal->resource(\%resource);
|
|
return $hal;
|
|
}
|
|
|
|
sub _item_rs {
|
|
my ($self, $c) = @_;
|
|
my $item_rs;
|
|
|
|
if($c->user->roles eq "admin") {
|
|
$item_rs = $c->model('DB')->resultset('voip_cf_time_sets');
|
|
} elsif ($c->user->roles eq "reseller") {
|
|
my $reseller_id = $c->user->reseller_id;
|
|
$item_rs = $c->model('DB')->resultset('voip_cf_time_sets')
|
|
->search_rs({
|
|
'reseller_id' => $reseller_id,
|
|
} , {
|
|
join => {'subscriber' => {'contract' => 'contact'} },
|
|
});
|
|
} elsif($c->user->roles eq "subscriberadmin" || $c->user->roles eq "subscriber") {
|
|
$item_rs = $c->model('DB')->resultset('voip_cf_time_sets')
|
|
->search_rs({
|
|
'subscriber_id' => $c->user->id,
|
|
});
|
|
}
|
|
|
|
return $item_rs;
|
|
}
|
|
|
|
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) = @_;
|
|
|
|
delete $resource->{id};
|
|
my $schema = $c->model('DB');
|
|
|
|
return unless $self->validate_form(
|
|
c => $c,
|
|
form => $form,
|
|
resource => $resource,
|
|
);
|
|
|
|
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;
|
|
}
|
|
|
|
if($c->user->roles eq "subscriber" || $c->user->roles eq "subscriberadmin") {
|
|
$resource->{subscriber_id} = $c->user->voip_subscriber->id;
|
|
}
|
|
|
|
my $b_subscriber = $schema->resultset('voip_subscribers')->find($resource->{subscriber_id});
|
|
unless ($b_subscriber) {
|
|
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid 'subscriber_id'.");
|
|
return;
|
|
}
|
|
my $subscriber = $b_subscriber->provisioning_voip_subscriber;
|
|
unless($subscriber) {
|
|
$self->error($c, HTTP_UNPROCESSABLE_ENTITY, "Invalid subscriber.");
|
|
last;
|
|
}
|
|
|
|
my $times = $resource->{times};
|
|
# enable tz and use_owner_tz params for PUT/PATCH/POST:
|
|
#$times = $self->apply_owner_timezone($c,$b_subscriber,$resource->{times},'deflate');
|
|
|
|
try {
|
|
$item->update({
|
|
name => $resource->{name},
|
|
subscriber_id => $subscriber->id,
|
|
})->discard_changes;
|
|
$item->voip_cf_periods->delete;
|
|
for my $t ( @$times ) {
|
|
delete $t->{time_set_id};
|
|
$item->create_related("voip_cf_periods", $t);
|
|
}
|
|
} catch($e) {
|
|
$c->log->error("failed to create cftimeset: $e");
|
|
$self->error($c, HTTP_INTERNAL_SERVER_ERROR, "Failed to create cftimeset.");
|
|
return;
|
|
};
|
|
|
|
return $item;
|
|
}
|
|
|
|
1;
|
|
# vim: set tabstop=4 expandtab:
|