Browse Source

Bug 16825: Add API route for getting an item

GET /api/v1/items/{item_id} Gets one Item

This patch adds route to get one item from koha.items table.

To test:
1. Apply patch
2. Open a browser tab on Koha staff and log in (to create CGISESSID
   cookie).
3. Send GET request to http://yourlibrary/api/v1/items/YYY
   where YYY is an existing itemnumber.
4. Make sure the returned data is correct.
5. Run unit tests in t/db_dependent/api/v1/items.t

Sponsored-by: Koha-Suomi Oy
Signed-off-by: Josef Moravec <josef.moravec@gmail.com>
Signed-off-by: Johanna Raisa <johanna.raisa@gmail.com>
Signed-off-by: Michal Denar <black23@gmail.com>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
remotes/origin/19.11.x
Lari Taskula 7 years ago
committed by Martin Renvoize
parent
commit
e9e6537fae
Signed by: martin.renvoize GPG Key ID: 422B469130441A0F
  1. 216
      Koha/REST/V1/Items.pm
  2. 3
      api/v1/swagger/definitions.json
  3. 183
      api/v1/swagger/definitions/item.json
  4. 3
      api/v1/swagger/parameters.json
  5. 9
      api/v1/swagger/parameters/item.json
  6. 3
      api/v1/swagger/paths.json
  7. 52
      api/v1/swagger/paths/items.json
  8. 81
      t/db_dependent/api/v1/items.t

216
Koha/REST/V1/Items.pm

@ -0,0 +1,216 @@
package Koha::REST::V1::Items;
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with Koha; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
use Modern::Perl;
use Mojo::Base 'Mojolicious::Controller';
use Mojo::JSON;
use C4::Auth qw( haspermission );
use C4::Items qw( GetHiddenItemnumbers );
use Koha::Items;
use Try::Tiny;
sub get {
my $c = shift->openapi->valid_input or return;
my $item;
try {
$item = Koha::Items->find($c->validation->param('item_id'));
return $c->render( status => 200, openapi => _to_api( $item->TO_JSON ) );
}
catch {
unless ( defined $item ) {
return $c->render( status => 404,
openapi => { error => 'Item not found'} );
}
if ( $_->isa('DBIx::Class::Exception') ) {
return $c->render( status => 500,
openapi => { error => $_->{msg} } );
}
else {
return $c->render( status => 500,
openapi => { error => "Something went wrong, check the logs."} );
}
};
}
=head3 _to_api
Helper function that maps unblessed Koha::Hold objects into REST api
attribute names.
=cut
sub _to_api {
my $item = shift;
# Rename attributes
foreach my $column ( keys %{ $Koha::REST::V1::Items::to_api_mapping } ) {
my $mapped_column = $Koha::REST::V1::Items::to_api_mapping->{$column};
if ( exists $item->{ $column }
&& defined $mapped_column )
{
# key != undef
$item->{ $mapped_column } = delete $item->{ $column };
}
elsif ( exists $item->{ $column }
&& !defined $mapped_column )
{
# key == undef
delete $item->{ $column };
}
}
return $item;
}
=head3 _to_model
Helper function that maps REST api objects into Koha::Hold
attribute names.
=cut
sub _to_model {
my $item = shift;
foreach my $attribute ( keys %{ $Koha::REST::V1::Items::to_model_mapping } ) {
my $mapped_attribute = $Koha::REST::V1::Items::to_model_mapping->{$attribute};
if ( exists $item->{ $attribute }
&& defined $mapped_attribute )
{
# key => !undef
$item->{ $mapped_attribute } = delete $item->{ $attribute };
}
elsif ( exists $item->{ $attribute }
&& !defined $mapped_attribute )
{
# key => undef / to be deleted
delete $item->{ $attribute };
}
}
return $item;
}
=head2 Global variables
=head3 $to_api_mapping
=cut
our $to_api_mapping = {
itemnumber => 'item_id',
biblionumber => 'biblio_id',
biblioitemnumber => undef,
barcode => 'external_id',
dateaccessioned => 'acquisition_date',
booksellerid => 'acquisition_source',
homebranch => 'home_library_id',
price => 'purchase_price',
replacementprice => 'replacement_price',
replacementpricedate => 'replacement_price_date',
datelastborrowed => 'last_checkout_date',
datelastseen => 'last_seen_date',
stack => undef,
notforloan => 'not_for_loan_status',
damaged => 'damaged_status',
damaged_on => 'damaged_date',
itemlost => 'lost_status',
itemlost_on => 'lost_date',
withdrawn => 'withdrawn',
withdrawn_on => 'withdrawn_date',
itemcallnumber => 'callnumber',
coded_location_qualifier => 'coded_location_qualifier',
issues => 'checkouts_count',
renewals => 'renewals_count',
reserves => 'holds_count',
restricted => 'restricted_status',
itemnotes => 'public_notes',
itemnotes_nonpublic => 'internal_notes',
holdingbranch => 'holding_library_id',
paidfor => undef,
timestamp => 'timestamp',
location => 'location',
permanent_location => 'permanent_location',
onloan => 'checked_out_date',
cn_source => 'call_number_source',
cn_sort => 'call_number_sort',
ccode => 'collection_code',
materials => 'materials_notes',
uri => 'uri',
itype => 'item_type',
more_subfields_xml => 'extended_subfields',
enumchron => 'serial_issue_number',
copynumber => 'copy_number',
stocknumber => 'inventory_number',
new_status => 'new_status'
};
=head3 $to_model_mapping
=cut
our $to_model_mapping = {
item_id => 'itemnumber',
biblio_id => 'biblionumber',
external_id => 'barcode',
acquisition_date => 'dateaccessioned',
acquisition_source => 'booksellerid',
home_library_id => 'homebranch',
purchase_price => 'price',
replacement_price => 'replacementprice',
replacement_price_date => 'replacementpricedate',
last_checkout_date => 'datelastborrowed',
last_seen_date => 'datelastseen',
not_for_loan_status => 'notforloan',
damaged_status => 'damaged',
damaged_date => 'damaged_on',
lost_status => 'itemlost',
lost_date => 'itemlost_on',
withdrawn => 'withdrawn',
withdrawn_date => 'withdrawn_on',
callnumber => 'itemcallnumber',
coded_location_qualifier => 'coded_location_qualifier',
checkouts_count => 'issues',
renewals_count => 'renewals',
holds_count => 'reserves',
restricted_status => 'restricted',
public_notes => 'itemnotes',
internal_notes => 'itemnotes_nonpublic',
holding_library_id => 'holdingbranch',
timestamp => 'timestamp',
location => 'location',
permanent_location => 'permanent_location',
checked_out_date => 'onloan',
call_number_source => 'cn_source',
call_number_sort => 'cn_sort',
collection_code => 'ccode',
materials_notes => 'materials',
uri => 'uri',
item_type => 'itype',
extended_subfields => 'more_subfields_xml',
serial_issue_number => 'enumchron',
copy_number => 'copynumber',
inventory_number => 'stocknumber',
new_status => 'new_status'
};
1;

3
api/v1/swagger/definitions.json

@ -23,6 +23,9 @@
"library": {
"$ref": "definitions/library.json"
},
"item": {
"$ref": "definitions/item.json"
},
"patron": {
"$ref": "definitions/patron.json"
},

183
api/v1/swagger/definitions/item.json

@ -0,0 +1,183 @@
{
"type": "object",
"properties": {
"item_id": {
"type": "integer",
"description": "Internal item identifier"
},
"biblio_id": {
"type": "integer",
"description": "Internal identifier for the parent bibliographic record"
},
"external_id": {
"type": ["string", "null"],
"description": "The item's barcode"
},
"acquisition_date": {
"type": ["string", "null"],
"format": "date",
"description": "The date the item was acquired"
},
"acquisition_source": {
"type": ["string", "null"],
"description": "Information about the acquisition source (it is not really a vendor id)"
},
"home_library_id": {
"type": ["string", "null"],
"description": "Internal library id for the library the item belongs to"
},
"purchase_price": {
"type": ["number", "null"],
"description": "Purchase price"
},
"replacement_price": {
"type": ["number", "null"],
"description": "Cost the library charges to replace the item (e.g. if lost)"
},
"replacement_price_date": {
"type": ["string", "null"],
"format": "date",
"description": "The date the replacement price is effective from"
},
"last_checkout_date": {
"type": ["string", "null"],
"format": "date",
"description": "The date the item was last checked out"
},
"last_seen_date": {
"type": ["string", "null"],
"format": "date",
"description": "The date the item barcode was last scanned"
},
"not_for_loan_status": {
"type": "integer",
"description": "Authorized value defining why this item is not for loan"
},
"damaged_status": {
"type": "integer",
"description": "Authorized value defining this item as damaged"
},
"damaged_date": {
"type": ["string", "null"],
"description": "The date and time an item was last marked as damaged, NULL if not damaged"
},
"lost_status": {
"type": "integer",
"description": "Authorized value defining this item as lost"
},
"lost_date": {
"type": ["string", "null"],
"format": "date-time",
"description": "The date and time an item was last marked as lost, NULL if not lost"
},
"withdrawn": {
"type": "integer",
"description": "Authorized value defining this item as withdrawn"
},
"withdrawn_date": {
"type": ["string", "null"],
"format": "date-time",
"description": "The date and time an item was last marked as withdrawn, NULL if not withdrawn"
},
"callnumber": {
"type": ["string", "null"],
"description": "Call number for this item"
},
"coded_location_qualifier": {
"type": ["string", "null"],
"description": "Coded location qualifier"
},
"checkouts_count": {
"type": ["integer", "null"],
"description": "Number of times this item has been checked out/issued"
},
"renewals_count": {
"type": ["integer", "null"],
"description": "Number of times this item has been renewed"
},
"holds_count": {
"type": ["integer", "null"],
"description": "Number of times this item has been placed on hold/reserved"
},
"restricted_status": {
"type": ["integer", "null"],
"description": "Authorized value defining use restrictions for this item"
},
"public_notes": {
"type": ["string", "null"],
"description": "Public notes on this item"
},
"internal_notes": {
"type": ["string", "null"],
"description": "Non-public notes on this item"
},
"holding_library_id": {
"type": ["string", "null"],
"description": "Library that is currently in possession item"
},
"timestamp": {
"type": "string",
"format": "date-time",
"description": "Date and time this item was last altered"
},
"location": {
"type": ["string", "null"],
"description": "Authorized value for the shelving location for this item"
},
"permanent_location": {
"type": ["string", "null"],
"description": "Linked to the CART and PROC temporary locations feature, stores the permanent shelving location"
},
"checked_out_date": {
"type": ["string", "null"],
"format": "date",
"description": "Defines if item is checked out (NULL for not checked out, and checkout date for checked out)"
},
"call_number_source": {
"type": ["string", "null"],
"description": "Classification source used on this item"
},
"call_number_sort": {
"type": ["string", "null"],
"description": "?"
},
"collection_code": {
"type": ["string", "null"],
"description": "Authorized value for the collection code associated with this item"
},
"materials_notes": {
"type": ["string", "null"],
"description": "Materials specified"
},
"uri": {
"type": ["string", "null"],
"description": "URL for the item"
},
"item_type": {
"type": ["string", "null"],
"description": "Itemtype defining the type for this item"
},
"extended_subfields": {
"type": ["string", "null"],
"description": "Additional 952 subfields in XML format"
},
"serial_issue_number": {
"type": ["string", "null"],
"description": "serial enumeration/chronology for the item"
},
"copy_number": {
"type": ["string", "null"],
"description": "Copy number"
},
"inventory_number": {
"type": ["string", "null"],
"description": "Inventory number"
},
"new_status": {
"type": ["string", "null"],
"description": "'new' value, whatever free-text information."
}
},
"additionalProperties": false,
"required": ["item_id", "biblio_id", "not_for_loan_status", "damaged_status", "lost_status", "withdrawn"]
}

3
api/v1/swagger/parameters.json

@ -17,6 +17,9 @@
"library_id_pp": {
"$ref": "parameters/library.json#/library_id_pp"
},
"item_id_pp": {
"$ref": "parameters/item.json#/item_id_pp"
},
"vendoridPathParam": {
"$ref": "parameters/vendor.json#/vendoridPathParam"
},

9
api/v1/swagger/parameters/item.json

@ -0,0 +1,9 @@
{
"item_id_pp": {
"name": "item_id",
"in": "path",
"description": "Internal item identifier",
"required": true,
"type": "integer"
}
}

3
api/v1/swagger/paths.json

@ -47,6 +47,9 @@
"/checkouts/{checkout_id}/allows_renewal": {
"$ref": "paths/checkouts.json#/~1checkouts~1{checkout_id}~1allows_renewal"
},
"/items/{item_id}": {
"$ref": "paths/items.json#/~1items~1{item_id}"
},
"/patrons": {
"$ref": "paths/patrons.json#/~1patrons"
},

52
api/v1/swagger/paths/items.json

@ -0,0 +1,52 @@
{
"/items/{item_id}": {
"get": {
"x-mojo-to": "Items#get",
"operationId": "getItem",
"tags": ["items"],
"parameters": [{
"$ref": "../parameters.json#/item_id_pp"
}
],
"consumes": ["application/json"],
"produces": ["application/json"],
"responses": {
"200": {
"description": "An item",
"schema": {
"$ref": "../definitions.json#/item"
}
},
"400": {
"description": "Missing or wrong parameters",
"schema": {
"$ref": "../definitions.json#/error"
}
},
"404": {
"description": "Item not found",
"schema": {
"$ref": "../definitions.json#/error"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "../definitions.json#/error"
}
},
"503": {
"description": "Under maintenance",
"schema": {
"$ref": "../definitions.json#/error"
}
}
},
"x-koha-authorization": {
"permissions": {
"catalogue": "1"
}
}
}
}
}

81
t/db_dependent/api/v1/items.t

@ -0,0 +1,81 @@
#!/usr/bin/env perl
# Copyright 2016 Koha-Suomi
#
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation; either version 3 of the License, or (at your option) any later
# version.
#
# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with Koha; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
use Modern::Perl;
use Test::More tests => 1;
use Test::Mojo;
use Test::Warn;
use t::lib::TestBuilder;
use t::lib::Mocks;
use C4::Auth;
use Koha::Items;
use Koha::Database;
my $schema = Koha::Database->new->schema;
my $builder = t::lib::TestBuilder->new;
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
my $t = Test::Mojo->new('Koha::REST::V1');
subtest 'get() tests' => sub {
plan tests => 9;
$schema->storage->txn_begin;
my $item = $builder->build_object( { class => 'Koha::Items' } );
my $patron = $builder->build_object({
class => 'Koha::Patrons',
value => { flags => 4 }
});
my $nonprivilegedpatron = $builder->build_object({
class => 'Koha::Patrons',
value => { flags => 0 }
});
my $password = 'thePassword123';
$nonprivilegedpatron->set_password({ password => $password, skip_validation => 1 });
my $userid = $nonprivilegedpatron->userid;
$t->get_ok( "//$userid:$password@/api/v1/items/" . $item->itemnumber )
->status_is(403)
->json_is( '/error' => 'Authorization failure. Missing required permission(s).' );
$patron->set_password({ password => $password, skip_validation => 1 });
$userid = $patron->userid;
$t->get_ok( "//$userid:$password@/api/v1/items/" . $item->itemnumber )
->status_is( 200, 'SWAGGER3.2.2' )
->json_is( '' => Koha::REST::V1::Items::_to_api( $item->TO_JSON ), 'SWAGGER3.3.2' );
my $non_existent_code = $item->itemnumber;
$item->delete;
$t->get_ok( "//$userid:$password@/api/v1/items/" . $non_existent_code )
->status_is(404)
->json_is( '/error' => 'Item not found' );
$schema->storage->txn_rollback;
};
Loading…
Cancel
Save