From 561260c04f6aca6a9fbd29f6dcb48c943412d417 Mon Sep 17 00:00:00 2001 From: Lari Taskula Date: Mon, 27 Jun 2016 16:03:49 +0300 Subject: [PATCH] Bug 16825: Add API route for getting an item GET /api/v1/items/{itemnumber} Gets one Item This patch adds route to get one item from koha.items table. This table has a column "itemnotes_nonpublic", which should not be returned if the user has no catalogue permission. To test: 1. Open a browser tab on Koha staff and log in (to create CGISESSID cookie). 2. Send GET request to http://yourlibrary/api/v1/items/YYY where YYY is an existing itemnumber. 3. Make sure the returned data is correct. 4. Run unit tests in t/db_dependent/api/v1/items.t Signed-off-by: jirislav Signed-off-by: Benjamin Rokseth https://bugs.koha-community.org/show_bug.cgi?id=16825 --- Koha/REST/V1/Item.pm | 45 +++++++++ api/v1/swagger/definitions.json | 3 + api/v1/swagger/definitions/item.json | 177 +++++++++++++++++++++++++++++++++++ api/v1/swagger/parameters.json | 3 + api/v1/swagger/parameters/item.json | 9 ++ api/v1/swagger/paths.json | 3 + api/v1/swagger/paths/items.json | 34 +++++++ t/db_dependent/api/v1/items.t | 117 +++++++++++++++++++++++ 8 files changed, 391 insertions(+) create mode 100644 Koha/REST/V1/Item.pm create mode 100644 api/v1/swagger/definitions/item.json create mode 100644 api/v1/swagger/parameters/item.json create mode 100644 api/v1/swagger/paths/items.json create mode 100644 t/db_dependent/api/v1/items.t diff --git a/Koha/REST/V1/Item.pm b/Koha/REST/V1/Item.pm new file mode 100644 index 0000000..03fd333 --- /dev/null +++ b/Koha/REST/V1/Item.pm @@ -0,0 +1,45 @@ +package Koha::REST::V1::Item; + +# 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 Koha::Items; + +sub get { + my ($c, $args, $cb) = @_; + + my $itemnumber = $c->param('itemnumber'); + my $item = Koha::Items->find($itemnumber); + unless ($item) { + return $c->$cb({error => "Item not found"}, 404); + } + + # Hide non-public itemnotes if user has no staff access + my $user = $c->stash('koha.user'); + unless ($user && haspermission($user->userid, {catalogue => 1})) { + $item->set({ itemnotes_nonpublic => undef }); + } + + return $c->$cb($item->unblessed, 200); +} + +1; diff --git a/api/v1/swagger/definitions.json b/api/v1/swagger/definitions.json index e4d7427..a6fa6cf 100644 --- a/api/v1/swagger/definitions.json +++ b/api/v1/swagger/definitions.json @@ -8,6 +8,9 @@ "hold": { "$ref": "definitions/hold.json" }, + "item": { + "$ref": "definitions/item.json" + }, "error": { "$ref": "definitions/error.json" } diff --git a/api/v1/swagger/definitions/item.json b/api/v1/swagger/definitions/item.json new file mode 100644 index 0000000..9c43f85 --- /dev/null +++ b/api/v1/swagger/definitions/item.json @@ -0,0 +1,177 @@ +{ + "type": "object", + "properties": { + "itemnumber": { + "type": "string", + "description": "internally assigned item identifier" + }, + "biblionumber": { + "type": "string", + "description": "internally assigned biblio identifier" + }, + "biblioitemnumber": { + "type": "string", + "description": "internally assigned biblio item identifier" + }, + "barcode": { + "type": ["string", "null"], + "description": "item barcode" + }, + "dateaccessioned": { + "type": ["string", "null"], + "description": "date the item was acquired or added to Koha" + }, + "booksellerid": { + "type": ["string", "null"], + "description": "where the item was purchased" + }, + "homebranch": { + "type": ["string", "null"], + "description": "library that owns this item" + }, + "price": { + "type": ["string", "null"], + "description": "purchase price" + }, + "replacementprice": { + "type": ["string", "null"], + "description": "cost the library charges to replace the item if it has been marked lost" + }, + "replacementpricedate": { + "type": ["string", "null"], + "description": "the date the price is effective from" + }, + "datelastborrowed": { + "type": ["string", "null"], + "description": "the date the item was last checked out/issued" + }, + "datelastseen": { + "type": ["string", "null"], + "description": "the date the item was last see (usually the last time the barcode was scanned or inventory was done)" + }, + "stack": { + "type": ["string", "null"], + "description": "?" + }, + "notforloan": { + "type": "string", + "description": "authorized value defining why this item is not for loan" + }, + "damaged": { + "type": "string", + "description": "authorized value defining this item as damaged" + }, + "itemlost": { + "type": "string", + "description": "authorized value defining this item as lost" + }, + "itemlost_on": { + "type": ["string", "null"], + "description": "the date and time an item was last marked as lost, NULL if not lost" + }, + "withdrawn": { + "type": "string", + "description": "authorized value defining this item as withdrawn" + }, + "withdrawn_on": { + "type": ["string", "null"], + "description": "the date and time an item was last marked as withdrawn, NULL if not withdrawn" + }, + "itemcallnumber": { + "type": ["string", "null"], + "description": "call number for this item" + }, + "coded_location_qualifier": { + "type": ["string", "null"], + "description": "coded location qualifier" + }, + "issues": { + "type": ["string", "null"], + "description": "number of times this item has been checked out/issued" + }, + "renewals": { + "type": ["string", "null"], + "description": "number of times this item has been renewed" + }, + "reserves": { + "type": ["string", "null"], + "description": "number of times this item has been placed on hold/reserved" + }, + "restricted": { + "type": ["string", "null"], + "description": "authorized value defining use restrictions for this item" + }, + "itemnotes": { + "type": ["string", "null"], + "description": "public notes on this item" + }, + "itemnotes_nonpublic": { + "type": ["string", "null"], + "description": "non-public notes on this item" + }, + "holdingbranch": { + "type": ["string", "null"], + "description": "library that is currently in possession item" + }, + "paidfor": { + "type": ["string", "null"], + "description": "?" + }, + "timestamp": { + "type": "string", + "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" + }, + "onloan": { + "type": ["string", "null"], + "description": "defines if item is checked out (NULL for not checked out, and checkout date for checked out)" + }, + "cn_source": { + "type": ["string", "null"], + "description": "classification source used on this item" + }, + "cn_sort": { + "type": ["string", "null"], + "description": "?" + }, + "ccode": { + "type": ["string", "null"], + "description": "authorized value for the collection code associated with this item" + }, + "materials": { + "type": ["string", "null"], + "description": "materials specified" + }, + "uri": { + "type": ["string", "null"], + "description": "URL for the item" + }, + "itype": { + "type": ["string", "null"], + "description": "itemtype defining the type for this item" + }, + "more_subfields_xml": { + "type": ["string", "null"], + "description": "additional 952 subfields in XML format" + }, + "enumchron": { + "type": ["string", "null"], + "description": "serial enumeration/chronology for the item" + }, + "copynumber": { + "type": ["string", "null"], + "description": "copy number" + }, + "stocknumber": { + "type": ["string", "null"], + "description": "inventory number" + } + } +} diff --git a/api/v1/swagger/parameters.json b/api/v1/swagger/parameters.json index b20e19f..38c19a7 100644 --- a/api/v1/swagger/parameters.json +++ b/api/v1/swagger/parameters.json @@ -7,5 +7,8 @@ }, "holdIdPathParam": { "$ref": "parameters/hold.json#/holdIdPathParam" + }, + "itemnumberPathParam": { + "$ref": "parameters/item.json#/itemnumberPathParam" } } diff --git a/api/v1/swagger/parameters/item.json b/api/v1/swagger/parameters/item.json new file mode 100644 index 0000000..c215adb --- /dev/null +++ b/api/v1/swagger/parameters/item.json @@ -0,0 +1,9 @@ +{ + "itemnumberPathParam": { + "name": "itemnumber", + "in": "path", + "description": "Internal item identifier", + "required": true, + "type": "integer" + } +} diff --git a/api/v1/swagger/paths.json b/api/v1/swagger/paths.json index f00b1b4..e36ebb3 100644 --- a/api/v1/swagger/paths.json +++ b/api/v1/swagger/paths.json @@ -5,6 +5,9 @@ "/holds/{reserve_id}": { "$ref": "paths/holds.json#/~1holds~1{reserve_id}" }, + "/items/{itemnumber}": { + "$ref": "paths/items.json#/~1items~1{itemnumber}" + }, "/patrons": { "$ref": "paths/patrons.json#/~1patrons" }, diff --git a/api/v1/swagger/paths/items.json b/api/v1/swagger/paths/items.json new file mode 100644 index 0000000..10a7eec --- /dev/null +++ b/api/v1/swagger/paths/items.json @@ -0,0 +1,34 @@ +{ + "/items/{itemnumber}": { + "get": { + "operationId": "getItem", + "tags": ["items"], + "parameters": [{ + "$ref": "../parameters.json#/itemnumberPathParam" + } + ], + "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" + } + } + } + } + } +} diff --git a/t/db_dependent/api/v1/items.t b/t/db_dependent/api/v1/items.t new file mode 100644 index 0000000..0536e0f --- /dev/null +++ b/t/db_dependent/api/v1/items.t @@ -0,0 +1,117 @@ +#!/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 => 12; +use Test::Mojo; +use t::lib::TestBuilder; + +use C4::Auth; +use C4::Biblio; +use C4::Context; +use C4::Items; + +use Koha::Database; +use Koha::Patron; +use Koha::Items; + +my $builder = t::lib::TestBuilder->new(); + +my $dbh = C4::Context->dbh; +$dbh->{AutoCommit} = 0; +$dbh->{RaiseError} = 1; + +$ENV{REMOTE_ADDR} = '127.0.0.1'; +my $t = Test::Mojo->new('Koha::REST::V1'); + +my $categorycode = $builder->build({ source => 'Category' })->{ categorycode }; +my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode }; +my $borrower = $builder->build({ + source => 'Borrower', + value => { + branchcode => $branchcode, + categorycode => $categorycode, + flags => 16, + } +}); + +my $librarian = $builder->build({ + source => "Borrower", + value => { + categorycode => $categorycode, + branchcode => $branchcode, + flags => 4, + }, +}); + +my ($session, $session2) = create_session($borrower, $librarian); + +my $biblio = $builder->build({ + source => 'Biblio' +}); +my $biblionumber = $biblio->{biblionumber}; +my $item = $builder->build({ + source => 'Item', + value => { + biblionumber => $biblionumber, + } +}); +my $itemnumber = $item->{itemnumber}; + +my $nonExistentItemnumber = -14362719; +my $tx = $t->ua->build_tx(GET => "/api/v1/items/$nonExistentItemnumber"); +$tx->req->cookies({name => 'CGISESSID', value => $session->id}); +$t->request_ok($tx) + ->status_is(404); + +$tx = $t->ua->build_tx(GET => "/api/v1/items/$itemnumber"); +$tx->req->cookies({name => 'CGISESSID', value => $session->id}); +$t->request_ok($tx) + ->status_is(200) + ->json_is('/itemnumber' => $itemnumber) + ->json_is('/biblionumber' => $biblionumber) + ->json_is('/itemnotes_nonpublic' => undef); + +$tx = $t->ua->build_tx(GET => "/api/v1/items/$itemnumber"); +$tx->req->cookies({name => 'CGISESSID', value => $session2->id}); +$t->request_ok($tx) + ->status_is(200) + ->json_is('/itemnumber' => $itemnumber) + ->json_is('/biblionumber' => $biblionumber) + ->json_is('/itemnotes_nonpublic' => $item->{itemnotes_nonpublic}); + +$dbh->rollback; + +sub create_session { + my (@borrowers) = @_; + + my @sessions; + foreach $borrower (@borrowers) { + my $session = C4::Auth::get_session(''); + $session->param('number', $borrower->{borrowernumber}); + $session->param('id', $borrower->{userid}); + $session->param('ip', '127.0.0.1'); + $session->param('lasttime', time()); + $session->flush; + push @sessions, $session; + } + + return @sessions; +} -- 1.9.1