Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add RESTful interface for querying reserves

  • Loading branch information...
commit 6f8534d9b3a6ab547b2c306affc5f44378dc41c8 1 parent 6d99db1
@ctfliblime ctfliblime authored
View
26 C4/Reserves.pm
@@ -607,20 +607,35 @@ sub GetHoldsQueueItems
return $total,$items;
}
+sub GetHighestPriority {
+ my $biblionumber = shift;
+
+ my ($priority) = C4::Context->dbh->selectrow_array(q{
+ SELECT max(priority) FROM reserves WHERE biblionumber = ?
+ }, undef, $biblionumber);
+ return $priority // 0;
+}
+
=item AddReserve
- AddReserve($branch,$borrowernumber,$biblionumber,$constraint,
+ AddReserve($branchcode,$borrowernumber,$biblionumber,$constraint,
$bibitems,$priority,$notes,$title,$checkitem,$found)
=cut
sub AddReserve {
my (
- $branch, $borrowernumber, $biblionumber,
+ $branchcode, $borrowernumber, $biblionumber,
$constraint, $bibitems, $priority, $resdate, $notes,
$title, $checkitem, $found
) = @_;
- my $dbh = C4::Context->dbh;
+
+ die 'Insufficient arguments provided to AddReserve'
+ unless (defined $branchcode && defined $borrowernumber && defined $biblionumber);
+ $priority //= GetHighestPriority($biblionumber) + 1;
+ $title //= C4::Biblio::GetBiblioData($biblionumber)->{title};
+ $constraint //= 'a';
+
my $const = lc substr( $constraint, 0, 1 );
my ($sec,$min,$hour,$day,$mon,$year,undef,undef,undef) = localtime();
@@ -656,9 +671,10 @@ sub AddReserve {
(?,?,?,?,?,
?,?,?,?,?,$expirationdate)
/;
+ my $dbh = C4::Context->dbh;
my $sth = $dbh->prepare($query);
$sth->execute(
- $borrowernumber, $biblionumber, $resdate, $branch,
+ $borrowernumber, $biblionumber, $resdate, $branchcode,
$const, $priority, $notes, $checkitem,
$found, $waitingdate
);
@@ -682,7 +698,7 @@ sub AddReserve {
}
UpdateStats(
- $branch,
+ $branchcode,
my $type = 'reserve',
my $amount,
my $other = $biblionumber,
View
51 Koha/Model/RdbModel.pm
@@ -0,0 +1,51 @@
+package Koha::Model::RdbModel;
+
+use Koha;
+use MooseX::Role::Parameterized;
+
+parameter 'nohandle' => {
+ is => 'ro',
+ isa => 'ArrayRef[Str]',
+ default => sub{[]},
+};
+
+role {
+ my $p = shift;
+ my %args = @_;
+
+ my $class = $args{consumer}->{package};
+ $class =~ s/Model/Schema/;
+
+ eval "require $class";
+ $class->import;
+
+ my @default_handlers = (
+ $class->meta->column_names,
+ (map {$_->name} $class->meta->foreign_keys),
+ qw(save load delete),
+ );
+
+ my @handlers = grep {!($_ ~~ @{$p->nohandle})} @default_handlers;
+
+ has 'db_obj' => (
+ is => 'rw',
+ isa => $class,
+ handles => \@handlers,
+ default => sub {
+ $class->new;
+ },
+ );
+
+ sub BUILD {
+ my $self = shift;
+ my $args = shift;
+
+ delete $args->{db_obj};
+ map {$self->db_obj->$_($args->{$_})} keys %{$args};
+ }
+
+};
+
+no Moose;
+
+1;
View
50 Koha/Model/Reserve.pm
@@ -0,0 +1,50 @@
+package Koha::Model::Reserve;
+
+use Koha;
+use C4::Reserves qw();
+use Moose;
+
+with 'Koha::Model::RdbModel' => {
+ nohandle => [qw(save delete)],
+};
+
+sub save {
+ my $self = shift;
+
+ if (defined $self->reservenumber) {
+ $self->db_obj->save();
+ C4::Reserves::_NormalizePriorities($self->biblionumber);
+ }
+ else {
+ my $reservenumber = C4::Reserves::AddReserve(
+ $self->branchcode,
+ $self->borrowernumber,
+ $self->biblionumber,
+ );
+ $self->db_obj(Koha::Schema::Reserve->new(reservenumber => $reservenumber)->load);
+ }
+
+ return;
+}
+
+sub delete {
+ my ($self) = @_;
+ C4::Reserves::CancelReserve($self->reservenumber);
+ return;
+}
+
+sub suspend {
+ my ($self, $resume_date) = @_;
+ C4::Reserves::SuspendReserve($self->reservenumber, $resume_date);
+ $self->load;
+}
+
+sub unsuspend {
+ my ($self) = @_;
+ C4::Reserves::ResumeReserve($self->reservenumber);
+ $self->load;
+}
+
+__PACKAGE__->meta->make_immutable;
+
+1;
View
46 Koha/Model/ReserveSet.pm
@@ -0,0 +1,46 @@
+package Koha::Model::ReserveSet;
+
+use Koha;
+use Koha::Schema::Reserve::Manager;
+use Moose;
+
+has 'reserves' => (
+ is => 'rw',
+ isa => 'ArrayRef[Koha::Model::Reserve]'
+ );
+
+has 'limits' => (
+ is => 'ro',
+ isa => 'HashRef',
+ default => sub {{}},
+ );
+
+sub BUILD {
+ my ($self) = @_;
+
+ my @where;
+ for my $l (qw(biblionumber borrowernumber branchcode)) {
+ if (defined $self->limits->{$l}) {
+ push @where, ($l => $self->limits->{$l});
+ }
+ }
+ my @limits = (
+ query => \@where,
+ sort_by => $self->limits->{sort_by} // 'priority ASC',
+ );
+ if (defined $self->limits->{limit}) {
+ push @limits, (
+ limit => $self->limits->{limit},
+ offset => $self->limits->{offset} // 0,
+ );
+ }
+
+ my $raw_reserves = Koha::Schema::Reserve::Manager->get_reserves(@limits);
+
+ my @cooked_reserves = map {Koha::Model::Reserve->new(db_obj => $_)} @$raw_reserves;
+ $self->reserves(\@cooked_reserves);
+}
+
+__PACKAGE__->meta->make_immutable;
+
+1;
View
229 Koha/Squatting/Reserve.pm
@@ -0,0 +1,229 @@
+use Koha;
+use Try::Tiny;
+use Carp;
+
+{
+ package Koha::Squatting::Reserve;
+ use Squatting;
+
+ our %CONFIG = (
+ );
+}
+
+{
+ package Koha::Squatting::Reserve::Controllers;
+ use Carp;
+ use Try::Tiny;
+ use HTTP::Exception;
+ use Koha::Model::Reserve;
+ use Koha::Model::ReserveSet;
+ use C4::Auth ();
+
+ our @returnable_types = (
+ 'text/html',
+ 'application/json'
+ );
+
+ sub _GetPreferredContentType {
+ my ($accept_header) = @_;
+
+ $accept_header =~ s/;.*$//;
+ my @accepted_types = split(/,/, $accept_header);
+ my @common = grep {$_ ~~ @returnable_types} @accepted_types;
+ return $common[0];
+ }
+
+ sub _SetContentType {
+ my $c = shift;
+ my $type_preference = _GetPreferredContentType($c->env->{HTTP_ACCEPT});
+ croak 'Unsupported content type' if !$type_preference;
+ $c->headers->{'Content-Type'} = $type_preference;
+
+ # only supporting JSON at this point
+ $c->status = 415 unless ($type_preference ~~ 'application/json');
+ return $type_preference;
+ }
+
+ sub _CheckAuth {
+ my ($self, $flags) = @_;
+
+ my ($status, undef)
+ = C4::Auth::check_cookie_auth($self->cookies->{CGISESSID}, $flags);
+
+ unless ($status ~~ 'ok') {
+ carp sprintf('Auth failed from %s accessing %s (cookie: %s)',
+ map {$self->env->{$_}} qw(REMOTE_ADDR REQUEST_URI HTTP_COOKIE)
+ );
+ HTTP::Exception::403->throw if ($status ne 'ok');
+ }
+ return 1;
+ }
+
+ sub ReserveShow {
+ my ($self, $reserve_id) = @_;
+
+ _CheckAuth($self, {reserveforothers => '*'});
+
+ try {
+ $self->v->{reserve}
+ = Koha::Model::Reserve->new(reservenumber => $reserve_id)->load;
+ $self->render('_rdb_obj', _SetContentType($self));
+ }
+ catch {
+ if (/^No such Koha::/) {
+ HTTP::Exception::404->throw;
+ }
+ else {
+ carp $_;
+ HTTP::Exception::500->throw;
+ }
+ };
+ }
+
+ sub ReserveSetShow {
+ my ($self) = @_;
+
+ _CheckAuth($self, {reserveforothers => '*'});
+
+ try {
+ my $results
+ = Koha::Model::ReserveSet->new(limits => $self->input);
+ $self->v->{reserveset} = ($results) ? $results->reserves : undef;
+ $self->v->{inflate} = $self->input->{inflate} // 0;
+ $self->render('_rdb_objset', _SetContentType($self));
+ }
+ catch {
+ carp $_;
+ HTTP::Exception::500->throw;
+ };
+ }
+
+ sub ReserveUpdate {
+ my ($self, $reservenumber) = @_;
+
+ _CheckAuth($self, {reserveforothers => 'edit_holds'});
+
+ my $input = $self->input;
+ my $r = Koha::Model::Reserve->new(reservenumber => $reservenumber);
+ $r->load;
+
+ $input->{priority} //= $r->priority;
+ $input->{branchcode} //= $r->branchcode;
+
+ if ($r->found ~~ 'S' && !$input->{is_suspended}) {
+ $r->unsuspend();
+ }
+ elsif (!($r->found ~~ 'S') && $input->{is_suspended}) {
+ $r->suspend($input->{resume_date});
+ }
+
+ $r->priority($input->{priority});
+ $r->branchcode($input->{branchcode});
+ $r->save;
+
+ return ReserveShow($self, $reservenumber);
+ }
+
+ sub ReserveCreate {
+ my ($self) = @_;
+
+ _CheckAuth($self, {reserveforothers => 'add_holds'});
+
+ my $input = $self->input;
+ my $r = Koha::Model::Reserve->new();
+ $r->borrowernumber($input->{borrowernumber});
+ $r->branchcode($input->{branchcode});
+ $r->biblionumber($input->{biblionumber});
+ try {
+ $r->save();
+ }
+ catch {
+ HTTP::Exception::401->throw;
+ };
+
+ return ReserveShow($self, $r->reservenumber);
+ }
+
+ sub ReserveCancel {
+ my ($self, $reservenumber) = @_;
+
+ _CheckAuth($self, {reserveforothers => 'delete_holds'});
+
+ try {
+ my $r = Koha::Model::Reserve->new(reservenumber => $reservenumber);
+ $r->delete;
+ $self->status = 204;
+ $self->render('deleted');
+ }
+ catch {
+ if (/^Unable to find reserve/) {
+ HTTP::Exception::404->throw;
+ }
+ else {
+ carp $_;
+ HTTP::Exception::500->throw;
+ }
+ };
+ }
+
+ our @C = (
+ C( ReserveSingle => ['/reserves/(\d+)'],
+ get => \&ReserveShow,
+ post => \&ReserveUpdate,
+ delete => \&ReserveCancel,
+ ),
+ C( ReserveSet => ['/reserves/'],
+ get => \&ReserveSetShow,
+ post => \&ReserveCreate,
+ ),
+ );
+}
+
+{
+ package Koha::Squatting::Reserve::Views;
+ use Rose::DB::Object::Helpers qw(as_tree as_json);
+ use JSON qw(to_json);
+
+ sub RenderRdbAsJson {
+ my ($self, $v) = @_;
+ as_json($v->{reserve});
+ }
+
+ sub RenderRdbSetAsJson {
+ my ($self, $v) = @_;
+
+ my @reserveset;
+ if ($v->{inflate}) {
+ for my $r (@{$v->{reserveset}}) {
+ my $t = as_tree($r->db_obj);
+ $t->{title} = $r->biblio->title;
+ $t->{author} = $r->biblio->author;
+ $t->{borrowername} = sprintf('%s, %s',
+ $r->borrower->surname,
+ $r->borrower->firstname);
+ $t->{borrowercard} = $r->borrower->cardnumber;
+ $t->{branchname} = $r->branchcode; #for now... no FK in db
+ $t->{itembarcode} = ($r->item) ? $r->item->barcode : undef;
+ $t->{uri} = R('ReserveSingle', $t->{reservenumber});
+ push @reserveset, $t;
+ }
+ }
+ else {
+ @reserveset = map {R('ReserveSingle', $_->reservenumber)} @{$v->{reserveset}};
+ }
+ to_json(\@reserveset);
+ }
+
+ our @V = (
+ V('text/html',
+ _ => sub {'Not yet supported'},
+ deleted => sub {q()},
+ ),
+ V('application/json',
+ _ => \&RenderRdbAsJson,
+ _rdb_objset => \&RenderRdbSetAsJson,
+ ),
+ );
+}
+
+1;
View
12 app.psgi
@@ -1,18 +1,18 @@
#!/usr/bin/env perl
-use Modern::Perl;
+use Koha;
use Plack::App::CGIBin;
use Plack::Builder;
use Data::Dumper;
my $app = Plack::App::CGIBin->new(root => $ENV{PERL5LIB})->to_app;
-my $svc = sub {
- my $env = shift;
- return [200, [ 'Content-type' => 'text/plain' ], [Dumper [$env, \%ENV]] ];
-};
+use Koha::Squatting::Reserve 'On::PSGI';
+my $reserves = Koha::Squatting::Reserve->init;
builder {
+ enable 'HTTPExceptions';
+ enable 'MethodOverride';
enable 'Deflater';
enable 'Static', path => qr{^/opac-tmpl/}, root => 'koha-tmpl/';
enable 'Static', path => qr{^/intranet-tmpl/}, root => 'koha-tmpl/';
@@ -20,6 +20,6 @@ builder {
enable '+C4::Plack::Rewrite';
enable '+C4::Plack::ScrubStatus';
+ mount '/reserves/' => sub {Koha::Squatting::Reserve->psgi(shift)};
mount '/' => $app;
- mount '/svc2' => $svc;
};
View
24 t/KohaModel.t
@@ -0,0 +1,24 @@
+#!/usr/bin/env perl
+
+use Koha;
+use Test::More tests => 6;
+
+BEGIN {
+ use_ok('Koha::Model::Reserve');
+ use_ok('Koha::Model::ReserveSet');
+}
+
+isa_ok (Koha::Model::Reserve->new(), 'Koha::Model::Reserve');
+
+my $rdb = Koha::Schema::Reserve->new(
+ biblionumber => 1234,
+ borrowernumber => 2345,
+ branchcode => 'ASDF',
+ reservedate => DateTime->now,
+ );
+my $r = Koha::Model::Reserve->new(db_obj => $rdb);
+isa_ok ($r, 'Koha::Model::Reserve', 'can acquire db_obj at new()');
+is $r->borrowernumber, 2345, 'can acquire db_obj attributes';
+
+my $rs = Koha::Model::ReserveSet->new(limits => {biblionumber => 1});
+isa_ok ($rs, 'Koha::Model::ReserveSet', 'Can generate ReserveSet');
View
33 t/SquattingReserve.t
@@ -0,0 +1,33 @@
+#!/usr/bin/env perl
+
+use Koha;
+use Test::More tests => 5;
+
+BEGIN {
+ use_ok('Koha::Squatting::Reserve');
+}
+
+my @hdrs = (
+ {
+ content => 'application/json,text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+ retval => 'application/json',
+ },
+ {
+ content => 'text/html,application/json,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+ retval => 'text/html',
+ },
+ {
+ content => 'application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+ retval => undef,
+ },
+ {
+ content => undef,
+ retval => undef,
+ },
+);
+
+for my $t (@hdrs) {
+ my $retval = Koha::Squatting::Reserve::Controllers::_GetPreferredContentType($t->{content});
+ is $retval, $t->{retval},
+ q{Check preferred content for } . ($t->{content} // '[undef]');
+}
Please sign in to comment.
Something went wrong with that request. Please try again.