Browse Source

Bug 13799: RESTful API with Mojolicious and Swagger2

Actual routes are:
  /borrowers
    Return a list of all borrowers in Koha

  /borrowers/{borrowernumber}
    Return the borrower identified by {borrowernumber}
    (eg. /borrowers/1)

There is a test file you can run with:
  $ prove t/db_dependent/rest/borrowers.t

All API stuff is in /api/v1 (except Perl modules)
So we have:
  /api/v1/script.cgi     CGI script
  /api/v1/swagger.json   Swagger specification

Change both OPAC and Intranet VirtualHosts to access the API,
so we have:
  http://OPAC/api/v1/swagger.json   Swagger specification
  http://OPAC/api/v1/{path}         API endpoint
  http://INTRANET/api/v1/swagger.json   Swagger specification
  http://INTRANET/api/v1/{path}         API endpoint

Add a (disabled) virtual host in Apache configuration api.HOSTNAME,
so we have:
  http://api.HOSTNAME/api/v1/swagger.json   Swagger specification
  http://api.HOSTNAME/api/v1/{path}         API endpoint

Add 'unblessed' subroutines to both Koha::Objects and Koha::Object to be
able to pass it to Mojolicious

Test plan:
  1/ Install Perl modules Mojolicious and Swagger2
  2/ perl Makefile.PL
  3/ make && make install
  4/ Change etc/koha-httpd.conf and copy it to the right place if needed
  5/ Reload Apache
  6/ Check that http://(OPAC|INTRANET)/api/v1/borrowers and
     http://(OPAC|INTRANET)/api/v1/borrowers/{borrowernumber} works

Optionally, you could verify that http://(OPAC|INTRANET)/vX/borrowers
(where X is an integer greater than 1) returns a 404 error

Signed-off-by: Alex Arnaud <alex.arnaud@biblibre.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
3.22.x
Julian Maurice 8 years ago
committed by Tomas Cohen Arazi
parent
commit
c83cd77411
  1. 10
      C4/Installer/PerlDependencies.pm
  2. 12
      Koha/Object.pm
  3. 12
      Koha/Objects.pm
  4. 28
      Koha/REST/V1.pm
  5. 29
      Koha/REST/V1/Borrowers.pm
  6. 6
      api/v1/app.pl
  7. 108
      api/v1/swagger.json
  8. 56
      etc/koha-httpd.conf
  9. 37
      t/db_dependent/api/v1/borrowers.t

10
C4/Installer/PerlDependencies.pm

@ -762,6 +762,16 @@ our $PERL_DEPS = {
'required' => '1',
'min_ver' => '0.05',
},
'Mojolicious' => {
'usage' => 'REST API',
'required' => '0',
'min_ver' => '5.54',
},
'Swagger2' => {
'usage' => 'REST API',
'required' => '0',
'min_ver' => '0.28',
},
};
1;

12
Koha/Object.pm

@ -207,6 +207,18 @@ sub id {
return $id;
}
=head3 $object->unblessed();
Returns an unblessed representation of object.
=cut
sub unblessed {
my ($self) = @_;
return { $self->_result->get_columns };
}
=head3 $object->_result();
Returns the internal DBIC Row object

12
Koha/Objects.pm

@ -181,6 +181,18 @@ sub as_list {
return wantarray ? @objects : \@objects;
}
=head3 Koha::Objects->unblessed
Returns an unblessed representation of objects.
=cut
sub unblessed {
my ($self) = @_;
return [ map { $_->unblessed } $self->as_list ];
}
=head3 Koha::Objects->_wrap
wraps the DBIC object in a corresponding Koha object

28
Koha/REST/V1.pm

@ -0,0 +1,28 @@
package Koha::REST::V1;
use Modern::Perl;
use Mojo::Base 'Mojolicious';
sub startup {
my $self = shift;
my $route = $self->routes->under->to(
cb => sub {
my $c = shift;
my $user = $c->param('user');
# Do the authentication stuff here...
$c->stash('user', $user);
return 1;
}
);
# Force charset=utf8 in Content-Type header for JSON responses
$self->types->type(json => 'application/json; charset=utf8');
$self->plugin(Swagger2 => {
route => $route,
url => $self->home->rel_file("api/v1/swagger.json"),
});
}
1;

29
Koha/REST/V1/Borrowers.pm

@ -0,0 +1,29 @@
package Koha::REST::V1::Borrowers;
use Modern::Perl;
use Mojo::Base 'Mojolicious::Controller';
use Koha::Borrowers;
sub list_borrowers {
my ($c, $args, $cb) = @_;
my $borrowers = Koha::Borrowers->search;
$c->$cb($borrowers->unblessed, 200);
}
sub get_borrower {
my ($c, $args, $cb) = @_;
my $borrower = Koha::Borrowers->find($args->{borrowernumber});
if ($borrower) {
return $c->$cb($borrower->unblessed, 200);
}
$c->$cb({error => "Borrower not found"}, 404);
}
1;

6
api/v1/app.pl

@ -0,0 +1,6 @@
#!/usr/bin/env perl
use Modern::Perl;
require Mojolicious::Commands;
Mojolicious::Commands->start_app('Koha::REST::V1');

108
api/v1/swagger.json

@ -0,0 +1,108 @@
{
"swagger": "2.0",
"info": {
"title": "Koha REST API",
"version": "1",
"license": {
"name": "GPL v3",
"url": "http://www.gnu.org/licenses/gpl.txt"
},
"contact": {
"name": "Koha Team",
"url": "http://koha-community.org/"
}
},
"basePath": "/api/v1",
"paths": {
"/borrowers": {
"get": {
"x-mojo-controller": "Koha::REST::V1::Borrowers",
"operationId": "listBorrowers",
"tags": ["borrowers"],
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "A list of borrowers",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/borrower"
}
}
}
}
}
},
"/borrowers/{borrowernumber}": {
"get": {
"x-mojo-controller": "Koha::REST::V1::Borrowers",
"operationId": "getBorrower",
"tags": ["borrowers"],
"parameters": [
{
"$ref": "#/parameters/borrowernumberPathParam"
}
],
"produces": [
"application/json"
],
"responses": {
"200": {
"description": "A borrower",
"schema": {
"$ref": "#/definitions/borrower"
}
},
"404": {
"description": "Borrower not found",
"schema": {
"$ref": "#/definitions/error"
}
}
}
}
}
},
"definitions": {
"borrower": {
"type": "object",
"properties": {
"borrowernumber": {
"$ref": "#/definitions/borrowernumber"
},
"cardnumber": {
"description": "library assigned ID number for borrowers"
},
"surname": {
"description": "borrower's last name"
},
"firstname": {
"description": "borrower's first name"
}
}
},
"borrowernumber": {
"description": "Borrower internal identifier"
},
"error": {
"type": "object",
"properties": {
"error": {
"description": "Error message",
"type": "string"
}
}
}
},
"parameters": {
"borrowernumberPathParam": {
"name": "borrowernumber",
"in": "path",
"description": "Internal borrower identifier",
"required": "true",
"type": "integer"
}
}
}

56
etc/koha-httpd.conf

@ -112,6 +112,20 @@
RewriteRule ^/bib/([^\/]*)/?$ /cgi-bin/koha/opac-detail\.pl?bib=$1 [PT]
RewriteRule ^/isbn/([^\/]*)/?$ /search?q=isbn:$1 [PT]
RewriteRule ^/issn/([^\/]*)/?$ /search?q=issn:$1 [PT]
# REST API configuration
Alias "/api" "__OPAC_CGI_DIR__/api"
<Directory __OPAC_CGI_DIR__/api>
Options +ExecCGI +FollowSymlinks
AddHandler cgi-script .pl
RewriteEngine On
RewriteBase /api/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{DOCUMENT_ROOT}/../api/$1/app.pl -f
RewriteRule ^(.*?)/.* $1/app.pl/api/$0 [L]
</Directory>
</IfModule>
</VirtualHost>
@ -214,5 +228,47 @@
RewriteRule ^/bib/([^\/]*)/?$ /cgi-bin/koha/detail\.pl?bib=$1 [PT]
RewriteRule ^/isbn/([^\/]*)/?$ /search?q=isbn:$1 [PT]
RewriteRule ^/issn/([^\/]*)/?$ /search?q=issn:$1 [PT]
# REST API configuration
Alias "/api" "__INTRANET_CGI_DIR__/api"
<Directory __INTRANET_CGI_DIR__/api>
Options +ExecCGI +FollowSymlinks
AddHandler cgi-script .pl
RewriteEngine On
RewriteBase /api/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{DOCUMENT_ROOT}/../api/$1/app.pl -f
RewriteRule ^(.*?)/.* $1/app.pl/api/$0 [L]
</Directory>
</IfModule>
</VirtualHost>
# Uncomment this VirtualHost to enable API access through
# api.__WEBSERVER_HOST__:__WEBSERVER_PORT__
#<VirtualHost __WEBSERVER_IP__:__WEBSERVER_PORT__>
# ServerAdmin __WEBMASTER_EMAIL__
# DocumentRoot __INTRANET_CGI_DIR__/api
# ServerName api.__WEBSERVER_HOST__:__WEBSERVER_PORT__
# SetEnv KOHA_CONF "__KOHA_CONF_DIR__/koha-conf.xml"
# SetEnv PERL5LIB "__PERL_MODULE_DIR__"
# ErrorLog __LOG_DIR__/koha-api-error_log
#
# <IfModule mod_rewrite.c>
# <Directory __INTRANET_CGI_DIR__/api>
# Options +ExecCGI +FollowSymlinks
# AddHandler cgi-script .pl
#
# RewriteEngine on
#
# RewriteRule ^api/(.*) $1 [L]
#
# RewriteCond %{REQUEST_FILENAME} !-f
# RewriteCond %{REQUEST_FILENAME} !-d
# RewriteCond %{DOCUMENT_ROOT}/$1/app.pl -f
# RewriteRule ^(.*?)/.* $1/app.pl/api/$0 [L]
# </Directory>
# </IfModule>
#</VirtualHost>

37
t/db_dependent/api/v1/borrowers.t

@ -0,0 +1,37 @@
#!/usr/bin/env perl
use Modern::Perl;
use Test::More tests => 6;
use Test::Mojo;
use C4::Context;
use Koha::Database;
use Koha::Borrower;
my $dbh = C4::Context->dbh;
$dbh->{AutoCommit} = 0;
$dbh->{RaiseError} = 1;
my $t = Test::Mojo->new('Koha::REST::V1');
my $categorycode = Koha::Database->new()->schema()->resultset('Category')->first()->categorycode();
my $branchcode = Koha::Database->new()->schema()->resultset('Branch')->first()->branchcode();
my $borrower = Koha::Borrower->new;
$borrower->categorycode( $categorycode );
$borrower->branchcode( $branchcode );
$borrower->surname("Test Surname");
$borrower->store;
my $borrowernumber = $borrower->borrowernumber;
$t->get_ok('/api/v1/borrowers')
->status_is(200);
$t->get_ok("/api/v1/borrowers/$borrowernumber")
->status_is(200)
->json_is('/borrowernumber' => $borrowernumber)
->json_is('/surname' => "Test Surname");
$dbh->rollback;
Loading…
Cancel
Save