From 87095625a46e2f49af4d7cfe7b7816934a6066a1 Mon Sep 17 00:00:00 2001 From: Aleisha Amohia Date: Thu, 1 Feb 2024 00:43:26 +0000 Subject: [PATCH] Bug 35967: Add REST API endpoint to list patron recalls This enhancement adds a REST API endpoint to list a patron's recalls: /api/v1/patrons/{patron_id}/recalls This depends on the logged in patron having the manage_recalls subpermission. To test: 1. Log in to the staff interface as your superlibrarian self (Patron A) 2. Go to Koha Administration -> Global system preferences. Enable the UseRecalls system preference 3. Set the relevant recalls circulation and fines rules 4. Search for an item (Item A) 5. Check out Item A to yourself (Patron A) 6. Log in to the OPAC as Patron B, a patron who does not have the manage_recalls permission 7. Search for Item A and request a recall 8. While still logged in to the OPAC as Patron B, hit this URL: https://your-opac-url/api/v1/patrons/patron-b-borrowernumber/recalls (swap out your URL and Patron B's borrowernumber) 9. Confirm you are given an error: "Authorization failure. Missing required permission(s)." 10. Log out of the OPAC and log back in, this time as Patron A 11. Hit the URL again https://your-opac-url/api/v1/patrons/patron-b-borrowernumber/recalls 12. Confirm you are able to view a list of Patron B's recalls 13. Confirm tests pass: t/db_dependent/api/v1/patrons_recalls.t Sponsored-by: Auckland University of Technology PA amended: QA follow-up: tidy Signed-off-by: Pedro Amorim --- Koha/REST/V1/Patrons/Recalls.pm | 63 ++++++++++++++++++ api/v1/swagger/definitions/recall.yaml | 81 +++++++++++++++++++++++ api/v1/swagger/definitions/recalls.yaml | 5 ++ api/v1/swagger/paths/patrons_recalls.yaml | 52 +++++++++++++++ api/v1/swagger/swagger.yaml | 9 +++ t/db_dependent/api/v1/patrons_recalls.t | 71 ++++++++++++++++++++ 6 files changed, 281 insertions(+) create mode 100644 Koha/REST/V1/Patrons/Recalls.pm create mode 100644 api/v1/swagger/definitions/recall.yaml create mode 100644 api/v1/swagger/definitions/recalls.yaml create mode 100644 api/v1/swagger/paths/patrons_recalls.yaml create mode 100755 t/db_dependent/api/v1/patrons_recalls.t diff --git a/Koha/REST/V1/Patrons/Recalls.pm b/Koha/REST/V1/Patrons/Recalls.pm new file mode 100644 index 00000000000..7a42fb778f3 --- /dev/null +++ b/Koha/REST/V1/Patrons/Recalls.pm @@ -0,0 +1,63 @@ +package Koha::REST::V1::Patrons::Recalls; + +# 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, see . + +use Modern::Perl; + +use Mojo::Base 'Mojolicious::Controller'; + +use Koha::Patrons; + +=head1 NAME + +Koha::REST::V1::Patrons::Recalls + +=head1 API + +=head2 Methods + +=head3 list + +Controller function that handles listing Koha::Recall objects for the requested patron + +=cut + +sub list { + my $c = shift->openapi->valid_input or return; + + my $patron = Koha::Patrons->find( $c->param('patron_id') ); + + unless ($patron) { + return $c->render( + status => 404, + openapi => { error => 'Patron not found' } + ); + } + + return try { + + my $recalls = $c->objects->search( $patron->recalls ); + + return $c->render( + status => 200, + openapi => $recalls + ); + } catch { + $c->unhandled_exception($_); + }; +} + +1; diff --git a/api/v1/swagger/definitions/recall.yaml b/api/v1/swagger/definitions/recall.yaml new file mode 100644 index 00000000000..5c310179aa3 --- /dev/null +++ b/api/v1/swagger/definitions/recall.yaml @@ -0,0 +1,81 @@ +--- +type: object +properties: + recall_id: + type: integer + description: Internal recall identifier + patron_id: + type: integer + description: Internal patron identifier + created_date: + type: + - string + - "null" + format: date-time + description: The date the recall was requested + biblio_id: + type: integer + description: Internal biblio identifier + pickup_library_id: + type: + - string + - "null" + description: Internal library identifier for the pickup library + completed_date: + type: + - string + - "null" + format: date-time + description: The date the recall was fulfilled + notes: + type: + - string + - "null" + description: Notes related to this recall + priority: + type: + - integer + - "null" + description: Where in the queue the patron sits (not yet implemented) + status: + type: + - string + - "null" + description: Status of the recall + enum: + - requested + - overdue + - waiting + - in_transit + - cancelled + - expired + - fulfilled + timestamp: + type: string + format: date-time + description: Timestamp for the latest recall update + item_id: + type: + - integer + - "null" + description: Internal item identifier + waiting_date: + type: + - string + - "null" + format: date-time + description: The date the item was marked as waiting for the patron at the library + expiration_date: + type: + - string + - "null" + format: date-time + description: The date the recall expires + completed: + type: boolean + description: Controls if the recall is fulfilled + item_level: + type: boolean + description: If the recall is requested at item level + +additionalProperties: false diff --git a/api/v1/swagger/definitions/recalls.yaml b/api/v1/swagger/definitions/recalls.yaml new file mode 100644 index 00000000000..afe4216a5ed --- /dev/null +++ b/api/v1/swagger/definitions/recalls.yaml @@ -0,0 +1,5 @@ +--- +type: array +items: + $ref: "recall.yaml" +additionalProperties: false diff --git a/api/v1/swagger/paths/patrons_recalls.yaml b/api/v1/swagger/paths/patrons_recalls.yaml new file mode 100644 index 00000000000..c4b2d994da6 --- /dev/null +++ b/api/v1/swagger/paths/patrons_recalls.yaml @@ -0,0 +1,52 @@ +--- +"/patrons/{patron_id}/recalls": + get: + x-mojo-to: Patrons::Recalls#list + operationId: getPatronRecalls + tags: + - recalls + summary: List recalls for a patron + parameters: + - $ref: "../swagger.yaml#/parameters/patron_id_pp" + - $ref: "../swagger.yaml#/parameters/match" + - $ref: "../swagger.yaml#/parameters/order_by" + - $ref: "../swagger.yaml#/parameters/page" + - $ref: "../swagger.yaml#/parameters/per_page" + - $ref: "../swagger.yaml#/parameters/q_param" + - $ref: "../swagger.yaml#/parameters/q_body" + - $ref: "../swagger.yaml#/parameters/request_id_header" + produces: + - application/json + responses: + "200": + description: The patron's recalls + schema: + type: array + items: + $ref: "../swagger.yaml#/definitions/recall" + "401": + description: Authentication required + schema: + $ref: "../swagger.yaml#/definitions/error" + "403": + description: Access forbidden + schema: + $ref: "../swagger.yaml#/definitions/error" + "404": + description: Patron not found + schema: + $ref: "../swagger.yaml#/definitions/error" + "500": + description: | + Internal server error. Possible `error_code` attribute values: + + * `internal_server_error` + schema: + $ref: "../swagger.yaml#/definitions/error" + "503": + description: Under maintenance + schema: + $ref: "../swagger.yaml#/definitions/error" + x-koha-authorization: + permissions: + recalls: manage_recalls diff --git a/api/v1/swagger/swagger.yaml b/api/v1/swagger/swagger.yaml index 227921d7957..563575784a3 100644 --- a/api/v1/swagger/swagger.yaml +++ b/api/v1/swagger/swagger.yaml @@ -140,6 +140,10 @@ definitions: $ref: ./definitions/preservation_processing.yaml quote: $ref: ./definitions/quote.yaml + recall: + $ref: ./definitions/recall.yaml + recalls: + $ref: ./definitions/recalls.yaml renewal: $ref: ./definitions/renewal.yaml renewals: @@ -427,6 +431,8 @@ paths: $ref: "./paths/patrons_password.yaml#/~1patrons~1{patron_id}~1password" "/patrons/{patron_id}/password/expiration_date": $ref: "./paths/patrons_password.yaml#/~1patrons~1{patron_id}~1password~1expiration_date" + "/patrons/{patron_id}/recalls": + $ref: "./paths/patrons_recalls.yaml#/~1patrons~1{patron_id}~1recalls" /preservation/config: $ref: ./paths/preservation_config.yaml#/~1preservation~1config /preservation/trains: @@ -1146,6 +1152,9 @@ tags: - description: "Manage quotes\n" name: quotes x-displayName: Quotes + - description: "Manage recalls\n" + name: recalls + x-displayName: Recalls - description: "Manage return claims\n" name: return_claims x-displayName: Return claims diff --git a/t/db_dependent/api/v1/patrons_recalls.t b/t/db_dependent/api/v1/patrons_recalls.t new file mode 100755 index 00000000000..2b7dc046e91 --- /dev/null +++ b/t/db_dependent/api/v1/patrons_recalls.t @@ -0,0 +1,71 @@ +#!/usr/bin/env perl + +# 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, see . + +use Modern::Perl; + +use Test::More tests => 1; +use Test::Mojo; + +use t::lib::TestBuilder; +use t::lib::Mocks; + +use Koha::Database; + +my $schema = Koha::Database->new->schema; +my $builder = t::lib::TestBuilder->new(); + +my $t = Test::Mojo->new('Koha::REST::V1'); + +t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 ); + +subtest 'list() tests' => sub { + + plan tests => 9; + + $schema->storage->txn_begin; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 2**27 } # recalls flag == 27 + } + ); + my $password = 'thePassword123'; + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $patron->userid; + + $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron->id . '/recalls' )->status_is( 200, 'SWAGGER3.2.2' ) + ->json_is( [] ); + + my $recall_1 = $builder->build_object( { class => 'Koha::Recalls', value => { patron_id => $patron->id } } ); + my $recall_2 = $builder->build_object( { class => 'Koha::Recalls', value => { patron_id => $patron->id } } ); + + $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $patron->id . '/recalls?_order_by=+me.recall_id' ) + ->status_is( 200, 'SWAGGER3.2.2' ) + ->json_is( '' => [ $recall_1->to_api, $recall_2->to_api ], 'Recalls retrieved' ); + + my $non_existent_patron = $builder->build_object( { class => 'Koha::Patrons' } ); + my $non_existent_patron_id = $non_existent_patron->id; + + # get rid of the patron + $non_existent_patron->delete; + + $t->get_ok( "//$userid:$password@/api/v1/patrons/" . $non_existent_patron_id . '/recalls' )->status_is(404) + ->json_is( '/error' => 'Patron not found' ); + + $schema->storage->txn_rollback; +}; -- 2.44.0