Bug 31800: Add REST endpoint to add a biblio
authorAgustin Moyano <agustinmoyano@theke.io>
Fri, 23 Dec 2022 20:15:29 +0000 (17:15 -0300)
committerTomas Cohen Arazi <tomascohen@theke.io>
Fri, 10 Mar 2023 14:47:18 +0000 (11:47 -0300)
To test:
1. Apply patch
2. Set RESTBasicAuth preference to true
3. Make a POST request to /api/v1/biblios with one of the following content type header
  - application/marcxml+xml
  - application/marc-in-json
  - application/marc
4. Add the following header to the request: "x-framework-id: <framework id>"
5. Check that the biblio is created
6. Sign off

Signed-off-by: Jan Kissig <jkissig@th-wildau.de>
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Koha/REST/V1/Biblios.pm
api/v1/swagger/paths/biblios.yaml
api/v1/swagger/swagger.yaml
t/db_dependent/api/v1/biblios.t

index f923255..4f58a40 100644 (file)
@@ -22,7 +22,8 @@ use Mojo::Base 'Mojolicious::Controller';
 use Koha::Biblios;
 use Koha::Ratings;
 use Koha::RecordProcessor;
-use C4::Biblio qw( DelBiblio );
+use C4::Biblio qw( DelBiblio AddBiblio );
+use C4::Search qw( FindDuplicate );
 
 use List::MoreUtils qw( any );
 use MARC::Record::MiJ;
@@ -480,4 +481,64 @@ sub set_rating {
     };
 }
 
+=head3 add
+
+Controller function that handles creating a biblio object
+
+=cut
+
+sub add {
+    my $c = shift->openapi->valid_input or return;
+
+    try {
+        my $body = $c->validation->param('Body');
+
+        my $flavour = $c->validation->param('x-marc-schema');
+        $flavour = C4::Context->preference('marcflavour') unless $flavour;
+
+        my $record;
+
+        my $frameworkcode = $c->validation->param('x-framework-id');
+        if ( $c->req->headers->content_type =~ m/application\/marcxml\+xml/ ) {
+            $record = MARC::Record->new_from_xml( $body, 'UTF-8', $flavour );
+        } elsif ( $c->req->headers->content_type =~ m/application\/marc-in-json/ ) {
+            $record = MARC::Record->new_from_mij_structure( $body );
+        } elsif ( $c->req->headers->content_type =~ m/application\/marc/ ) {
+            $record = MARC::Record->new_from_usmarc( $body );
+        } else {
+            return $c->render(
+                status  => 406,
+                openapi => [
+                    "application/marcxml+xml",
+                    "application/marc-in-json",
+                    "application/marc"
+                ]
+            );
+        }
+
+        my ( $duplicatebiblionumber, $duplicatetitle );
+            ( $duplicatebiblionumber, $duplicatetitle ) = FindDuplicate($record);
+
+        my $confirm_not_duplicate = $c->validation->param('x-confirm-not-duplicate');
+
+        return $c->render(
+            status  => 400,
+            openapi => {
+                error => "Duplicate biblio $duplicatebiblionumber"
+            }
+        ) unless !$duplicatebiblionumber || $confirm_not_duplicate;
+
+        my ( $biblionumber, $oldbibitemnum );
+            ( $biblionumber, $oldbibitemnum ) = AddBiblio( $record, $frameworkcode );
+
+        $c->render(
+            status  => 200,
+            openapi => { id => $biblionumber }
+        );
+    }
+    catch {
+        $c->unhandled_exception($_);
+    };
+}
+
 1;
index 782de21..c19ec73 100644 (file)
@@ -1,4 +1,61 @@
 ---
+"/biblios":
+  post:
+    x-mojo-to: Biblios#add
+    operationId: addBiblio
+    tags:
+      - biblios
+    summary: Add biblio
+    parameters:
+      - name: Body
+        in: body
+        description: A JSON object or the Marc string describing a biblio
+        required: true
+        schema:
+          type:
+            - string
+            - object
+      - $ref: "../swagger.yaml#/parameters/framework_id_header"
+      - $ref: "../swagger.yaml#/parameters/marc_schema_header"
+      - $ref: "../swagger.yaml#/parameters/confirm_not_duplicate_header"
+    produces:
+      - application/json
+    responses:
+      "200":
+        description: A biblio
+      "400":
+        description: Bad request
+        schema:
+          $ref: "../swagger.yaml#/definitions/error"
+      "401":
+        description: Authentication required
+        schema:
+          $ref: "../swagger.yaml#/definitions/error"
+      "403":
+        description: Access forbidden
+        schema:
+          $ref: "../swagger.yaml#/definitions/error"
+      "406":
+        description: Not acceptable
+        schema:
+          type: array
+          description: Accepted content-types
+          items:
+            type: string
+      "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:
+        editcatalogue: edit_catalogue
 "/biblios/{biblio_id}":
   get:
     x-mojo-to: Biblios#get
index f7ead6c..6f7d697 100644 (file)
@@ -153,6 +153,8 @@ paths:
     $ref: "./paths/authorised_values.yaml#/~1authorised_value_categories~1{authorised_value_category_name}~1authorised_values"
   "/authorities/{authority_id}":
     $ref: paths/authorities.yaml#/~1authorities~1{authority_id}
+  "/biblios":
+    $ref: "./paths/biblios.yaml#/~1biblios"
   "/biblios/{biblio_id}":
     $ref: "./paths/biblios.yaml#/~1biblios~1{biblio_id}"
   "/biblios/{biblio_id}/checkouts":
@@ -372,6 +374,27 @@ parameters:
     name: authority_id
     required: true
     type: integer
+  framework_id_header:
+    description: Framework id. Use when content type is not application/json
+    name: x-framework-id
+    in: header
+    required: false
+    type: string
+  marc_schema_header:
+    description: March schema. One of MARC21 or UNIMARC
+    name: x-marc-schema
+    in: header
+    required: false
+    type: string
+    enum:
+      - MARC21
+      - UNIMARC
+  confirm_not_duplicate_header:
+    description: Confirm the posted element is not a duplicate
+    name: x-confirm-not-duplicate
+    in: header
+    required: false
+    type: integer
   identity_provider_id_pp:
     description: Authentication provider internal identifier
     in: path
index b4e98b1..c5909ee 100755 (executable)
@@ -20,7 +20,7 @@ use Modern::Perl;
 use utf8;
 use Encode;
 
-use Test::More tests => 8;
+use Test::More tests => 9;
 use Test::MockModule;
 use Test::Mojo;
 use Test::Warn;
@@ -713,3 +713,432 @@ subtest 'set_rating() tests' => sub {
     $schema->storage->txn_rollback;
 
 };
+
+
+subtest 'post() tests' => sub {
+
+    plan tests => 13;
+
+    $schema->storage->txn_begin;
+
+    my $patron = $builder->build_object(
+        {
+            class => 'Koha::Patrons',
+            value => { flags => 0 } # no permissions
+        }
+    );
+    my $password = 'thePassword123';
+    $patron->set_password( { password => $password, skip_validation => 1 } );
+    my $userid = $patron->userid;
+
+    my $frameworkcode = 'BKS';
+    my $marcxml = q|<?xml version="1.0" encoding="UTF-8"?>
+<record
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.loc.gov/MARC21/slim http://www.loc.gov/standards/marcxml/schema/MARC21slim.xsd"
+    xmlns="http://www.loc.gov/MARC21/slim">
+
+  <leader>01102pam a2200289 a 7500</leader>
+  <controlfield tag="001">2504398</controlfield>
+  <controlfield tag="005">20200421093816.0</controlfield>
+  <controlfield tag="008">920610s1993    caub         s001 0 eng  </controlfield>
+  <datafield tag="010" ind1=" " ind2=" ">
+    <subfield code="a">   92021731 </subfield>
+  </datafield>
+  <datafield tag="020" ind1=" " ind2=" ">
+    <subfield code="a">05200784381 (Test marcxml)</subfield>
+  </datafield>
+  <datafield tag="020" ind1=" " ind2=" ">
+    <subfield code="a">05200784461 (Test marcxml)</subfield>
+  </datafield>
+  <datafield tag="040" ind1=" " ind2=" ">
+    <subfield code="a">DLC</subfield>
+    <subfield code="c">DLC</subfield>
+    <subfield code="d">DLC</subfield>
+  </datafield>
+  <datafield tag="041" ind1="0" ind2=" ">
+    <subfield code="a">enggrc</subfield>
+  </datafield>
+  <datafield tag="050" ind1="0" ind2="0">
+    <subfield code="a">PA522</subfield>
+    <subfield code="b">.M38 1993</subfield>
+  </datafield>
+  <datafield tag="082" ind1="0" ind2="0">
+    <subfield code="a">480</subfield>
+    <subfield code="2">20</subfield>
+  </datafield>
+  <datafield tag="100" ind1="1" ind2=" ">
+    <subfield code="a">Mastronarde, Donald J.</subfield>
+    <subfield code="9">389</subfield>
+  </datafield>
+  <datafield tag="245" ind1="1" ind2="0">
+    <subfield code="a">Introduction to Attic Greek (Using marcxml) /</subfield>
+    <subfield code="c">Donald J. Mastronarde.</subfield>
+  </datafield>
+  <datafield tag="260" ind1=" " ind2=" ">
+    <subfield code="a">Berkeley :</subfield>
+    <subfield code="b">University of California Press,</subfield>
+    <subfield code="c">c1993.</subfield>
+  </datafield>
+  <datafield tag="300" ind1=" " ind2=" ">
+    <subfield code="a">ix, 425 p. :</subfield>
+    <subfield code="b">maps ;</subfield>
+    <subfield code="c">26 cm.</subfield>
+  </datafield>
+  <datafield tag="500" ind1=" " ind2=" ">
+    <subfield code="a">Includes index.</subfield>
+  </datafield>
+  <datafield tag="650" ind1=" " ind2="0">
+    <subfield code="a">Attic Greek dialect</subfield>
+    <subfield code="9">7</subfield>
+  </datafield>
+  <datafield tag="856" ind1="4" ind2="2">
+    <subfield code="3">Contributor biographical information</subfield>
+    <subfield code="u">http://www.loc.gov/catdir/bios/ucal051/92021731.html</subfield>
+  </datafield>
+  <datafield tag="856" ind1="4" ind2="2">
+    <subfield code="3">Publisher description</subfield>
+    <subfield code="u">http://www.loc.gov/catdir/description/ucal041/92021731.html</subfield>
+  </datafield>
+  <datafield tag="906" ind1=" " ind2=" ">
+    <subfield code="a">7</subfield>
+    <subfield code="b">cbc</subfield>
+    <subfield code="c">orignew</subfield>
+    <subfield code="d">1</subfield>
+    <subfield code="e">ocip</subfield>
+    <subfield code="f">19</subfield>
+    <subfield code="g">y-gencatlg</subfield>
+  </datafield>
+  <datafield tag="942" ind1=" " ind2=" ">
+    <subfield code="2">ddc</subfield>
+    <subfield code="c">BK</subfield>
+  </datafield>
+  <datafield tag="955" ind1=" " ind2=" ">
+    <subfield code="a">pc05 to ea00 06-11-92; ea04 to SCD 06-11-92; fd11 06-11-92 (PA522.M...); fr21 06-12-92; fs62 06-15-92; CIP ver. pv07 11-12-93</subfield>
+  </datafield>
+  <datafield tag="999" ind1=" " ind2=" ">
+    <subfield code="c">3</subfield>
+    <subfield code="d">3</subfield>
+  </datafield>
+</record>|;
+
+    my $mij = q|{
+  "fields": [
+    {
+      "001": "2504398"
+    },
+    {
+      "005": "20200421093816.0"
+    },
+    {
+      "008": "920610s1993    caub         s001 0 eng  "
+    },
+    {
+      "010": {
+        "ind1": " ",
+        "subfields": [
+          {
+            "a": "   92021731 "
+          }
+        ],
+        "ind2": " "
+      }
+    },
+    {
+      "020": {
+        "subfields": [
+          {
+            "a": "05200784382 (Test mij)"
+          }
+        ],
+        "ind2": " ",
+        "ind1": " "
+      }
+    },
+    {
+      "020": {
+        "subfields": [
+          {
+            "a": "05200784462 (Test mij)"
+          }
+        ],
+        "ind1": " ",
+        "ind2": " "
+      }
+    },
+    {
+      "040": {
+        "subfields": [
+          {
+            "a": "DLC"
+          },
+          {
+            "c": "DLC"
+          },
+          {
+            "d": "DLC"
+          }
+        ],
+        "ind2": " ",
+        "ind1": " "
+      }
+    },
+    {
+      "041": {
+        "ind2": " ",
+        "subfields": [
+          {
+            "a": "enggrc"
+          }
+        ],
+        "ind1": "0"
+      }
+    },
+    {
+      "050": {
+        "subfields": [
+          {
+            "a": "PA522"
+          },
+          {
+            "b": ".M38 1993"
+          }
+        ],
+        "ind1": "0",
+        "ind2": "0"
+      }
+    },
+    {
+      "082": {
+        "subfields": [
+          {
+            "a": "480"
+          },
+          {
+            "2": "20"
+          }
+        ],
+        "ind2": "0",
+        "ind1": "0"
+      }
+    },
+    {
+      "100": {
+        "ind2": " ",
+        "subfields": [
+          {
+            "a": "Mastronarde, Donald J."
+          },
+          {
+            "9": "389"
+          }
+        ],
+        "ind1": "1"
+      }
+    },
+    {
+      "245": {
+        "ind1": "1",
+        "subfields": [
+          {
+            "a": "Introduction to Attic Greek  (Using mij) /"
+          },
+          {
+            "c": "Donald J. Mastronarde."
+          }
+        ],
+        "ind2": "0"
+      }
+    },
+    {
+      "260": {
+        "subfields": [
+          {
+            "a": "Berkeley :"
+          },
+          {
+            "b": "University of California Press,"
+          },
+          {
+            "c": "c1993."
+          }
+        ],
+        "ind2": " ",
+        "ind1": " "
+      }
+    },
+    {
+      "300": {
+        "ind1": " ",
+        "subfields": [
+          {
+            "a": "ix, 425 p. :"
+          },
+          {
+            "b": "maps ;"
+          },
+          {
+            "c": "26 cm."
+          }
+        ],
+        "ind2": " "
+      }
+    },
+    {
+      "500": {
+        "subfields": [
+          {
+            "a": "Includes index."
+          }
+        ],
+        "ind1": " ",
+        "ind2": " "
+      }
+    },
+    {
+      "650": {
+        "subfields": [
+          {
+            "a": "Attic Greek dialect"
+          },
+          {
+            "9": "7"
+          }
+        ],
+        "ind2": "0",
+        "ind1": " "
+      }
+    },
+    {
+      "856": {
+        "subfields": [
+          {
+            "3": "Contributor biographical information"
+          },
+          {
+            "u": "http://www.loc.gov/catdir/bios/ucal051/92021731.html"
+          }
+        ],
+        "ind2": "2",
+        "ind1": "4"
+      }
+    },
+    {
+      "856": {
+        "ind1": "4",
+        "subfields": [
+          {
+            "3": "Publisher description"
+          },
+          {
+            "u": "http://www.loc.gov/catdir/description/ucal041/92021731.html"
+          }
+        ],
+        "ind2": "2"
+      }
+    },
+    {
+      "906": {
+        "subfields": [
+          {
+            "a": "7"
+          },
+          {
+            "b": "cbc"
+          },
+          {
+            "c": "orignew"
+          },
+          {
+            "d": "1"
+          },
+          {
+            "e": "ocip"
+          },
+          {
+            "f": "19"
+          },
+          {
+            "g": "y-gencatlg"
+          }
+        ],
+        "ind1": " ",
+        "ind2": " "
+      }
+    },
+    {
+      "942": {
+        "subfields": [
+          {
+            "2": "ddc"
+          },
+          {
+            "c": "BK"
+          }
+        ],
+        "ind2": " ",
+        "ind1": " "
+      }
+    },
+    {
+      "955": {
+        "subfields": [
+          {
+            "a": "pc05 to ea00 06-11-92; ea04 to SCD 06-11-92; fd11 06-11-92 (PA522.M...); fr21 06-12-92; fs62 06-15-92; CIP ver. pv07 11-12-93"
+          }
+        ],
+        "ind2": " ",
+        "ind1": " "
+      }
+    },
+    {
+      "999": {
+        "subfields": [
+          {
+            "c": "3"
+          },
+          {
+            "d": "3"
+          }
+        ],
+        "ind1": " ",
+        "ind2": " "
+      }
+    }
+  ],
+  "leader": "01102pam a2200289 a 8500"
+}|;
+    my $marc = q|01102pam a2200289 a 9500001000800000005001700008008004100025010001700066020002800083020003500111040001800146041001100164050002100175082001200196100003200208245005800240260005600298300003300354500002000387650002700407856009500434856008700529906004500616942001200661955013000673999000900803\1e2504398\1e20200421093816.0\1e920610s1993    caub         s001 0 eng  \1e  \1fa   92021731 \1e  \1fa05200784383 (Test usmarc)\1e  \1fa05200784463 (Test usmarc)\1e  \1faDLC\1fcDLC\1fdDLC\1e\1faenggrc\1e00\1faPA522\1fb.M38 1993\1e00\1fa480\1f220\1e\1faMastronarde, Donald J.\1f9389\1e10\1faIntroduction to Attic Greek  (Using usmarc) /\1fcDonald J. Mastronarde.\1e  \1faBerkeley :\1fbUniversity of California Press,\1fcc1993.\1e  \1faix, 425 p. :\1fbmaps ;\1fc26 cm.\1e  \1faIncludes index.\1e 0\1faAttic Greek dialect\1f97\1e42\1f3Contributor biographical information\1fuhttp://www.loc.gov/catdir/bios/ucal051/92021731.html\1e42\1f3Publisher description\1fuhttp://www.loc.gov/catdir/description/ucal041/92021731.html\1e  \1fa7\1fbcbc\1fcorignew\1fd1\1feocip\1ff19\1fgy-gencatlg\1e  \1f2ddc\1fcBK\1e  \1fapc05 to ea00 06-11-92; ea04 to SCD 06-11-92; fd11 06-11-92 (PA522.M...); fr21 06-12-92; fs62 06-15-92; CIP ver. pv07 11-12-93\1e  \1fc3\1fd3\1e\1d|;
+
+    $t->post_ok("//$userid:$password@/api/v1/biblios")
+      ->status_is(403, 'Not enough permissions makes it return the right code');
+
+    # Add permissions
+    $builder->build(
+        {
+            source => 'UserPermission',
+            value  => {
+                borrowernumber => $patron->borrowernumber,
+                module_bit     => 9,
+                code           => 'edit_catalogue'
+            }
+        }
+    );
+
+    $t->post_ok("//$userid:$password@/api/v1/biblios" => {'Content-Type' => 'application/marcxml+xml', 'x-framework-id' => $frameworkcode, "x-march-schema" => 'INVALID'})
+      ->status_is(400, 'Invalid header x-marc-schema');
+
+    $t->post_ok("//$userid:$password@/api/v1/biblios" => {'Content-Type' => 'application/marcxml+xml', 'x-framework-id' => $frameworkcode} => $marcxml)
+      ->status_is(200)
+      ->json_has('/id');
+
+    $t->post_ok("//$userid:$password@/api/v1/biblios" => {'Content-Type' => 'application/marc-in-json', 'x-framework-id' => $frameworkcode, 'x-confirm-not-duplicate' => 1} => $mij)
+      ->status_is(200)
+      ->json_has('/id');
+
+    $t->post_ok("//$userid:$password@/api/v1/biblios" => {'Content-Type' => 'application/marc', 'x-framework-id' => $frameworkcode} => $marc)
+      ->status_is(200)
+      ->json_has('/id');
+
+    $schema->storage->txn_rollback;
+};
\ No newline at end of file