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>
This commit is contained in:
parent
314f3e22d9
commit
c83cd77411
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env perl
|
||||
|
||||
use Modern::Perl;
|
||||
|
||||
require Mojolicious::Commands;
|
||||
Mojolicious::Commands->start_app('Koha::REST::V1');
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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…
Reference in New Issue