From 3048a670abefe32b6f2d4554ef704eea5130e7a8 Mon Sep 17 00:00:00 2001 From: Julian Maurice Date: Tue, 7 Dec 2021 16:19:11 +0100 Subject: [PATCH] Bug 28201: Add API route to create a biblio Example usage: POST /api/v1/biblios Accept: application/json { "marcxml": "...", "framework_id": "FA" } It can return data in the same formats as GET /api/v1/biblios/:biblio_id depending on the value of the Accept request header: - application/json - application/marcxml+xml - application/marc-in-json - application/marc - text/plain Test plan: 1. Try requesting this endpoint with your favorite API tool (I recommend https://github.com/frigus02/RESTer) 2. Run `prove t/db_dependent/api/v1/biblios/post.t` --- Koha/REST/V1/Biblios.pm | 71 +++++++++++++++++++++++++++- api/v1/swagger/paths.yaml | 2 + api/v1/swagger/paths/biblios.yaml | 58 +++++++++++++++++++++++ t/db_dependent/api/v1/biblios/post.t | 68 ++++++++++++++++++++++++++ 4 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 t/db_dependent/api/v1/biblios/post.t diff --git a/Koha/REST/V1/Biblios.pm b/Koha/REST/V1/Biblios.pm index 839d117f56..e74f94ad90 100644 --- a/Koha/REST/V1/Biblios.pm +++ b/Koha/REST/V1/Biblios.pm @@ -21,7 +21,7 @@ use Mojo::Base 'Mojolicious::Controller'; use Koha::Biblios; use Koha::RecordProcessor; -use C4::Biblio qw( DelBiblio ); +use C4::Biblio qw( AddBiblio DelBiblio ); use List::MoreUtils qw( any ); use MARC::Record::MiJ; @@ -32,6 +32,75 @@ use Try::Tiny qw( catch try ); =head2 Methods +=head2 add + +=cut + +sub add { + my $c = shift->openapi->valid_input or return; + + my $body = $c->validation->param('body'); + my $marcxml = $body->{marcxml}; + my $frameworkcode = $body->{framework_id} // ''; + + try { + my $record = MARC::Record->new_from_xml($marcxml); + my ($biblionumber) = AddBiblio($record, $frameworkcode); + + my $attributes; + $attributes = { prefetch => [ 'metadata' ] } # don't prefetch metadata if not needed + unless $c->req->headers->accept =~ m/application\/json/; + + my $biblio = Koha::Biblios->find( { biblionumber => $biblionumber }, $attributes ); + + if ( $c->req->headers->accept =~ m/application\/json/ ) { + return $c->render( + status => 200, + json => $biblio->to_api + ); + } + else { + my $record = $biblio->metadata->record; + + $c->respond_to( + marcxml => { + status => 200, + format => 'marcxml', + text => $record->as_xml_record + }, + mij => { + status => 200, + format => 'mij', + data => $record->to_mij + }, + marc => { + status => 200, + format => 'marc', + text => $record->as_usmarc + }, + txt => { + status => 200, + format => 'text/plain', + text => $record->as_formatted + }, + any => { + status => 406, + openapi => [ + "application/json", + "application/marcxml+xml", + "application/marc-in-json", + "application/marc", + "text/plain" + ] + } + ); + } + } + catch { + $c->unhandled_exception($_); + }; +} + =head3 get Controller function that handles retrieving a single biblio object diff --git a/api/v1/swagger/paths.yaml b/api/v1/swagger/paths.yaml index 6908fa8bb1..7ffd03aa80 100644 --- a/api/v1/swagger/paths.yaml +++ b/api/v1/swagger/paths.yaml @@ -13,6 +13,8 @@ $ref: paths/acquisitions_funds.yaml#/~1acquisitions~1funds "/article_requests/{article_request_id}": $ref: paths/article_requests.yaml#/~1article_requests~1{article_request_id} +"/biblios": + $ref: paths/biblios.yaml#/~1biblios "/biblios/{biblio_id}": $ref: paths/biblios.yaml#/~1biblios~1{biblio_id} "/biblios/{biblio_id}/checkouts": diff --git a/api/v1/swagger/paths/biblios.yaml b/api/v1/swagger/paths/biblios.yaml index 7c64399ecb..cdd51cd802 100644 --- a/api/v1/swagger/paths/biblios.yaml +++ b/api/v1/swagger/paths/biblios.yaml @@ -1,4 +1,62 @@ --- +"/biblios": + post: + x-mojo-to: Biblios#add + operationId: addBiblio + tags: + - biblios + summary: Add biblio + parameters: + - name: body + in: body + required: true + schema: + type: object + required: + - marcxml + properties: + marcxml: + type: string + framework_id: + type: string + additionalProperties: false + consumes: + - application/json + produces: + - application/json + - application/marcxml+xml + - application/marc-in-json + - application/marc + - text/plain + responses: + "201": + description: A biblio + "401": + description: Authentication required + schema: + $ref: ../definitions.yaml#/error + "403": + description: Access forbidden + schema: + $ref: ../definitions.yaml#/error + "406": + description: Not acceptable + schema: + type: array + description: Accepted content-types + items: + type: string + "500": + description: Internal server error + schema: + $ref: ../definitions.yaml#/error + "503": + description: Under maintenance + schema: + $ref: ../definitions.yaml#/error + x-koha-authorization: + permissions: + catalogue: "1" "/biblios/{biblio_id}": get: x-mojo-to: Biblios#get diff --git a/t/db_dependent/api/v1/biblios/post.t b/t/db_dependent/api/v1/biblios/post.t new file mode 100644 index 0000000000..90dff14aaf --- /dev/null +++ b/t/db_dependent/api/v1/biblios/post.t @@ -0,0 +1,68 @@ +#!/usr/bin/env perl + +use Modern::Perl; + +use Test::More tests => 17; +use Test::Mojo; + +use t::lib::Mocks; +use t::lib::TestBuilder; + +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'); + +$schema->storage->txn_begin; + +my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { flags => 0 } + } +); +my $password = 'thePassword123'; +$patron->set_password( { password => $password, skip_validation => 1 } ); +my $userid = $patron->userid; + +my $biblio = $builder->build_sample_biblio({ + title => 'The unbearable lightness of being', + author => 'Milan Kundera' +}); + +my $url = "//$userid:$password@/api/v1/biblios"; +my $marcxml = $biblio->metadata->record->as_xml_record; + +$t->post_ok($url, json => { marcxml => $marcxml }) + ->status_is(403); + +$patron->flags(4)->store; + +$t->post_ok($url, { Accept => 'application/weird+format' }, json => { marcxml => $marcxml }) + ->status_is(406) + ->json_is( [ "application/json", + "application/marcxml+xml", + "application/marc-in-json", + "application/marc", + "text/plain" ] ); + +$t->post_ok($url, { Accept => 'application/json' }, json => { marcxml => $marcxml }) + ->status_is(200) + ->json_is( '/title', 'The unbearable lightness of being' ) + ->json_is( '/author', 'Milan Kundera' ); + +$t->post_ok($url, { Accept => 'application/marcxml+xml' }, json => { marcxml => $marcxml }) + ->status_is(200); + +$t->post_ok($url, { Accept => 'application/marc-in-json' }, json => { marcxml => $marcxml }) + ->status_is(200); + +$t->post_ok($url, { Accept => 'application/marc' }, json => { marcxml => $marcxml }) + ->status_is(200); + +$t->post_ok($url, { Accept => 'text/plain' }, json => { marcxml => $marcxml }) + ->status_is(200); + +$schema->storage->txn_rollback; -- 2.30.2