RT-Extension-REST2/lib/RT/Extension/REST2/Resource/Collection.pm
package RT::Extension::REST2::Resource::Collection;
use strict;
use warnings;
use Moose;
use namespace::autoclean;
extends 'RT::Extension::REST2::Resource';
use Scalar::Util qw( blessed );
use Web::Machine::FSM::States qw( is_status_code );
use Module::Runtime qw( require_module );
use RT::Extension::REST2::Util qw( expand_uid format_datetime );
use POSIX qw( ceil );
use Encode;
has 'collection_class' => (
is => 'ro',
isa => 'ClassName',
);
has 'collection' => (
is => 'ro',
isa => 'RT::SearchBuilder',
required => 1,
lazy_build => 1,
);
sub _build_collection {
my $self = shift;
my $collection = $self->collection_class->new( $self->current_user );
return $collection;
}
sub setup_paging {
my $self = shift;
my $per_page = $self->request->param('per_page') || 20;
if ( $per_page !~ /^\d+$/ ) { $per_page = 20 }
elsif ( $per_page == 0 ) { $per_page = 20 }
elsif ( $per_page > 100 ) { $per_page = 100 }
$self->collection->RowsPerPage($per_page);
my $page = $self->request->param('page') || 1;
if ( $page !~ /^\d+$/ ) { $page = 1 }
elsif ( $page == 0 ) { $page = 1 }
$self->collection->GotoPage($page - 1);
}
sub setup_ordering {
my $self = shift;
my @orderby_cols;
my @orders = $self->request->param('order');
foreach my $orderby ($self->request->param('orderby')) {
$orderby = decode_utf8($orderby);
my $order = shift @orders || 'ASC';
$order = uc(decode_utf8($order));
$order = 'ASC' unless $order eq 'DESC';
push @orderby_cols, {FIELD => $orderby, ORDER => $order};
}
$self->collection->OrderByCols(@orderby_cols)
if @orderby_cols;
}
sub limit_collection {
my $self = shift;
my $collection = $self->collection;
$collection->{'find_disabled_rows'} = 1
if $self->request->param('find_disabled_rows');
return 1;
}
sub search {
my $self = shift;
$self->setup_paging;
$self->setup_ordering;
return $self->limit_collection;
}
sub serialize {
my $self = shift;
my $collection = $self->collection;
my @results;
my @fields = defined $self->request->param('fields') ? split(/,/, $self->request->param('fields')) : ();
while (my $item = $collection->Next) {
my $result = $self->serialize_record( $item->UID );
# Allow selection of desired fields
if ($result) {
for my $field (@fields) {
if ( $field eq '_hyperlinks' ) {
my $class = ref $item;
$class =~ s!^RT::!RT::Extension::REST2::Resource::!;
if ( $class->require ) {
my $object = $class->new(
record_class => ref $item,
record_id => $item->id,
record => $item,
request => $self->request,
response => Plack::Response->new,
);
if ( $object->can('hypermedia_links') ) {
$result->{$field} = $object->hypermedia_links;
}
else {
RT->Logger->warning("_hyperlinks is not supported by $class, skipping");
}
}
else {
RT->Logger->warning("Couldn't load $class, skipping _hyperlinks");
}
}
else {
my $field_result = $self->expand_field($item, $field);
$result->{$field} = $field_result if defined $field_result;
}
}
}
push @results, $result;
}
my %results = (
count => scalar(@results) + 0,
total => $collection->CountAll + 0,
per_page => $collection->RowsPerPage + 0,
page => ($collection->FirstRow / $collection->RowsPerPage) + 1,
items => \@results,
);
my $uri = $self->request->uri;
my @query_form = $uri->query_form;
# find page and if it is set, delete it and its value.
for my $i (0..$#query_form) {
if ($query_form[$i] eq 'page') {
delete @query_form[$i, $i + 1];
last;
}
}
$results{pages} = ceil($results{total} / $results{per_page});
if ($results{page} < $results{pages}) {
my $page = $results{page} + 1;
$uri->query_form( @query_form, page => $results{page} + 1 );
$results{next_page} = $uri->as_string;
};
if ($results{page} > 1) {
# If we're beyond the last page, set prev_page as the last page
# available, otherwise, the previous page.
$uri->query_form( @query_form, page => ($results{page} > $results{pages} ? $results{pages} : $results{page} - 1) );
$results{prev_page} = $uri->as_string;
};
return \%results;
}
sub serialize_record {
my $self = shift;
my $record = shift;
return expand_uid($record);
}
# XXX TODO: Bulk update via DELETE/PUT on a collection resource?
sub charsets_provided { [ 'utf-8' ] }
sub default_charset { 'utf-8' }
sub content_types_provided { [
{ 'application/json' => 'to_json' },
] }
sub to_json {
my $self = shift;
my $status = $self->search;
return $status if is_status_code($status);
return \400 unless $status;
return JSON::to_json($self->serialize, { pretty => 1 });
}
sub finish_request {
my $self = shift;
# Ensure the collection object is destroyed before the request finishes, for
# any cleanup that may need to happen (i.e. TransactionBatch).
$self->clear_collection;
return $self->SUPER::finish_request(@_);
}
__PACKAGE__->meta->make_immutable;
1;