From 9ef8d92fc86a6fa30a221715518b6bc4cea9cff2 Mon Sep 17 00:00:00 2001 From: Aleisha Amohia Date: Wed, 17 Dec 2025 04:00:12 +0000 Subject: [PATCH] Bug 41463: [WIP] oai_sets REST API endpoint --- Koha/OAI/Set.pm | 66 +++++++++++++++ Koha/REST/V1/OAI/Sets.pm | 58 +++++++++++++ api/v1/swagger/definitions/oai_set.yaml | 21 +++++ api/v1/swagger/paths/oai_sets.yaml | 47 +++++++++++ api/v1/swagger/swagger.yaml | 4 + t/db_dependent/api/v1/oai_sets.t | 105 ++++++++++++++++++++++++ 6 files changed, 301 insertions(+) create mode 100644 Koha/REST/V1/OAI/Sets.pm create mode 100644 api/v1/swagger/definitions/oai_set.yaml create mode 100644 api/v1/swagger/paths/oai_sets.yaml create mode 100755 t/db_dependent/api/v1/oai_sets.t diff --git a/Koha/OAI/Set.pm b/Koha/OAI/Set.pm index 5b52edc3168..be38b7447d3 100644 --- a/Koha/OAI/Set.pm +++ b/Koha/OAI/Set.pm @@ -33,6 +33,72 @@ Koha::OAI::Set - Koha OaiSet Object class =cut +=head3 biblionumbers + + my @biblionumbers = $set->biblionumbers->as_list; + +Returns the biblionumbers contained in this set. + +=cut + +sub biblionumbers { + my ( $self, $params ) = @_; + + return $self->_result->oai_sets_biblios->search( {}, { columns => ['biblionumber'] } ); +} + +=head3 biblios + + my @biblios = $set->biblios->as_list; + +Returns the Koha::Biblio objects that exist in the catalog for records in this set. + +=cut + +sub biblios { + my ( $self, $params ) = @_; + + return Koha::Biblios->search( { biblionumber => scalar $self->biblionumbers } ); +} + +=head3 to_api + + my $json = $set->to_api; + +Overloaded method that returns a JSON representation of the Koha::OAI::Set object, +suitable for API output. + +=cut + +sub to_api { + my ( $self, $params ) = @_; + + my $json_set = $self->SUPER::to_api($params); + + return unless $json_set; + + $json_set->{total_count} = $self->biblionumbers->count; + + $json_set->{catalog_count} = $self->biblios->count; + + return $json_set; +} + +=head3 to_api_mapping + +This method returns the mapping for representing a Koha::OAI::Set object +on the API. + +=cut + +sub to_api_mapping { + return { + id => 'set_id', + spec => 'spec', + name => 'name', + }; +} + =head2 Internal methods =head3 _type diff --git a/Koha/REST/V1/OAI/Sets.pm b/Koha/REST/V1/OAI/Sets.pm new file mode 100644 index 00000000000..8bbda476acd --- /dev/null +++ b/Koha/REST/V1/OAI/Sets.pm @@ -0,0 +1,58 @@ +package Koha::REST::V1::OAI::Sets; + +# 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 Try::Tiny qw( catch try ); + +use Koha::OAI::Sets; + +=head1 NAME + +Koha::REST::V1::OAI::Sets; + +=head1 API + +=head2 Methods + +=head3 get + +Controller function that handles retrieving an OAI set + +=cut + +sub get { + my $c = shift->openapi->valid_input or return; + + return try { + my $set = Koha::OAI::Sets->find( $c->param('set_id') ); + + return $c->render_resource_not_found("OAI set") + unless $set; + + return $c->render( + status => 200, + openapi => $c->objects->to_api($set), + ); + } catch { + $c->unhandled_exception($_); + } +} + +1; diff --git a/api/v1/swagger/definitions/oai_set.yaml b/api/v1/swagger/definitions/oai_set.yaml new file mode 100644 index 00000000000..57cd6b545da --- /dev/null +++ b/api/v1/swagger/definitions/oai_set.yaml @@ -0,0 +1,21 @@ +--- +type: object +properties: + set_id: + type: integer + description: Internal OAI set identifier + spec: + type: string + description: Indicates a hierarchy of sets + name: + type: string + description: Name of set + total_count: + type: integer + description: Number of total items in the set + catalog_count: + type: integer + description: Number of items which exist both in the set and in the catalog +additionalProperties: false +required: + - set_id diff --git a/api/v1/swagger/paths/oai_sets.yaml b/api/v1/swagger/paths/oai_sets.yaml new file mode 100644 index 00000000000..d87857773d0 --- /dev/null +++ b/api/v1/swagger/paths/oai_sets.yaml @@ -0,0 +1,47 @@ +--- +"/oai_sets/{set_id}": + get: + x-mojo-to: OAI::Sets#get + operationId: getOAISet + tags: + - oai_sets + summary: Get OAI set + parameters: + - name: set_id + in: path + description: Internal OAI set identifier + required: true + type: integer + produces: + - application/json + responses: + "200": + description: An OAI set + schema: + $ref: "../swagger.yaml#/definitions/oai_set" + "400": + description: Bad request + 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: + parameters: manage_oai_sets diff --git a/api/v1/swagger/swagger.yaml b/api/v1/swagger/swagger.yaml index dcb0a3cc66e..8ac7f73d130 100644 --- a/api/v1/swagger/swagger.yaml +++ b/api/v1/swagger/swagger.yaml @@ -142,6 +142,8 @@ definitions: $ref: ./definitions/list.yaml merge_biblios: $ref: ./definitions/merge_biblios.yaml + oai_set: + $ref: ./definitions/oai_set.yaml order: $ref: ./definitions/order.yaml order_claim: @@ -483,6 +485,8 @@ paths: $ref: "./paths/libraries.yaml#/~1libraries~1{library_id}~1cash_registers" "/libraries/{library_id}/desks": $ref: "./paths/libraries.yaml#/~1libraries~1{library_id}~1desks" + "/oai_sets/{set_id}": + $ref: "./paths/oai_sets.yaml#/~1oai_sets~1{set_id}" "/oauth/login/{provider_code}/{interface}": $ref: ./paths/oauth.yaml#/~1oauth~1login~1{provider_code}~1{interface} /oauth/token: diff --git a/t/db_dependent/api/v1/oai_sets.t b/t/db_dependent/api/v1/oai_sets.t new file mode 100755 index 00000000000..7d021aa24eb --- /dev/null +++ b/t/db_dependent/api/v1/oai_sets.t @@ -0,0 +1,105 @@ +#!/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 => 2; +use Test::NoWarnings; +use Test::Mojo; + +use t::lib::TestBuilder; +use t::lib::Mocks; + +use Koha::OAI::Sets; +use Koha::OAI::Set::Biblios; +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 'get() tests' => sub { + + plan tests => 8; + + $schema->storage->txn_begin; + + Koha::OAI::Set::Biblios->search->delete; + Koha::OAI::Sets->search->delete; + + my $librarian = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 25**2 } # parameters flag = 3 + } + ); + my $password = 'thePassword123'; + $librarian->set_password( { password => $password, skip_validation => 1 } ); + my $userid = $librarian->userid; + + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } + ); + + $patron->set_password( { password => $password, skip_validation => 1 } ); + my $unauth_userid = $patron->userid; + + $t->get_ok("//$userid:$password@/api/v1/")->status_is(200)->json_is( [] ); + + my $set = $builder->build_object( + { + class => 'Koha::OAI::Sets', + value => { spec => 'A' } + } + ); + my $set_id = $set->id; + + my $set_item_deleted_1 = $builder->build_object( + { + class => 'Koha::OAI::Set::Biblios', + value => { set_id => $set_id, biblionumber => 123456789 } + } + ); + + my $set_item_deleted_2 = $builder->build_object( + { + class => 'Koha::OAI::Set::Biblios', + value => { set_id => $set_id, biblionumber => 987654321 } + } + ); + + my $biblio = $builder->build_sample_biblio(); + + my $set_item_in_catalog = $builder->build_object( + { + class => 'Koha::OAI::Set::Biblios', + value => { set_id => $set_id, biblionumber => $biblio->id } + } + ); + + $t->get_ok("//$userid:$password@/api/v1/oai_sets/$set_id")->status_is(200)->json_is( [ $set->to_api ] ); + + # Unauthorized access + $t->get_ok("//$unauth_userid:$password@/api/v1/oai_sets/$set_id")->status_is(403); + + $schema->storage->txn_rollback; +}; -- 2.39.5