Commit 97acf6b9 authored by Keith Jolley's avatar Keith Jolley

Start development of experimental RESTful interface.

parent a04de2f5
#!/usr/bin/perl -T
#bigsrest.pl REST interface for BIGSdb
#Written by Keith Jolley
#Copyright (c) 2014, University of Oxford
#E-mail: keith.jolley@zoo.ox.ac.uk
#
#This file is part of Bacterial Isolate Genome Sequence Database (BIGSdb).
#
#BIGSdb 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.
#
#BIGSdb 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 BIGSdb. If not, see <http://www.gnu.org/licenses/>.
package BIGSdb::main;
use strict;
use warnings;
use 5.010;
###########Local configuration################################
use constant {
CONFIG_DIR => '/etc/bigsdb',
LIB_DIR => '/usr/local/lib',
DBASE_CONFIG_DIR => '/etc/bigsdb/dbases',
HOST => 'localhost',
PORT => 5432,
USER => 'apache',
PASSWORD => ''
};
#######End Local configuration################################
use Log::Log4perl qw(get_logger); #Also need Log::Dispatch::File
use lib (LIB_DIR);
use BIGSdb::REST::Interface;
Log::Log4perl->init_once( CONFIG_DIR . '/rest_logging.conf' );
BIGSdb::REST::Interface->new(
{
config_dir => CONFIG_DIR,
lib_dir => LIB_DIR,
dbase_config_dir => DBASE_CONFIG_DIR,
host => HOST,
port => PORT,
user => USER,
password => PASSWORD
}
);
......@@ -15,6 +15,9 @@ prefs_db=bigsdb_prefs
#Name of authentication database.
auth_db=bigsdb_auth
#Name of database describing resources for REST interface
rest_db=bigsdb_rest
#Specify number of days that job files will remain on server.
results_deleted_days=7
......
#Change log levels for different modules below
#DEBUG: Useful for debugging.
#INFO: Informative messages, e.g. file and directory opens.
#WARN: Configuration inconsistency, application can continue to function.
#ERROR: Something wrong, can continue running, but may perform unexpectedly.
#FATAL: The application is about to terminate abnormally.
log4perl.logger.BIGSdb.Dataconnector=WARN, A1
log4perl.logger.BIGSdb.Datastore=WARN, A1
log4perl.logger.BIGSdb.Application_Initiate=WARN, A1
log4perl.logger.BIGSdb.Application_Authentication=WARN, A1
log4perl.logger.BIGSdb.Locus=WARN, A1
log4perl.logger.BIGSdb.Scheme=WARN, A1
log4perl.logger.BIGSdb.Page=WARN, A1
log4perl.logger.BIGSdb.Script=WARN, A1
log4perl.appender.A1=Log::Dispatch::File
log4perl.appender.A1.filename=/var/log/bigsdb_rest.log
log4perl.appender.A1.mode=append
log4perl.appender.A1.layout=Log::Log4perl::Layout::PatternLayout
log4perl.appender.A1.layout.ConversionPattern=%d [%p] %F{1}:%L - %m%n
log4perl.appender.A1.binmode=:utf8
#Written by Keith Jolley
#Copyright (c) 2014, University of Oxford
#E-mail: keith.jolley@zoo.ox.ac.uk
#
#This file is part of Bacterial Isolate Genome Sequence Database (BIGSdb).
#
#BIGSdb 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.
#
#BIGSdb 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 BIGSdb. If not, see <http://www.gnu.org/licenses/>.
package BIGSdb::REST::Interface;
use strict;
use warnings;
use 5.010;
use parent qw(BIGSdb::Application);
use Dancer2;
use Error qw(:try);
use Log::Log4perl qw(get_logger);
my $logger = get_logger('BIGSdb.Application_Initiate');
use BIGSdb::Utils;
use BIGSdb::REST::Routes::Isolates;
use BIGSdb::REST::Routes::Resources;
use BIGSdb::REST::Routes::Users;
use constant PAGE_SIZE => 100;
sub new {
my ( $class, $options ) = @_;
my $self = {};
$self->{'system'} = {};
$self->{'config'} = {};
$self->{'instance'} = undef;
$self->{'xmlHandler'} = undef;
$self->{'dataConnector'} = BIGSdb::Dataconnector->new;
$self->{'datastore'} = undef;
$self->{'db'} = undef;
$self->{'config_dir'} = $options->{'config_dir'};
$self->{'lib_dir'} = $options->{'lib_dir'};
$self->{'dbase_config_dir'} = $options->{'dbase_config_dir'};
$self->{'host'} = $options->{'host'};
$self->{'port'} = $options->{'port'};
$self->{'user'} = $options->{'user'};
$self->{'password'} = $options->{'password'};
bless( $self, $class );
$self->_initiate;
$self->{'dataConnector'}->initiate( $self->{'system'}, $self->{'config'} );
set serializer => 'JSON'; #Mutable isn't working with Dancer2.
set self => $self;
dance;
return $self;
}
sub _initiate {
my ( $self, ) = @_;
$self->read_config_file( $self->{'config_dir'} );
$self->read_host_mapping_file( $self->{'config_dir'} );
return;
}
#Read database configs and connect before entering route.
hook before => sub {
my $self = setting('self');
my $request_uri = request->uri();
$self->{'instance'} = $request_uri =~ /^\/db\/([\w\d\-_]+)/ ? $1 : '';
my $full_path = "$self->{'dbase_config_dir'}/$self->{'instance'}/config.xml";
if ( !$self->{'instance'} ) {
undef $self->{'system'};
$self->{'system'}->{'db'} = $self->{'config'}->{'rest_db'};
} elsif ( !-e $full_path ) {
undef $self->{'system'};
return;
} else {
$self->{'xmlHandler'} = BIGSdb::Parser->new;
my $parser = XML::Parser::PerlSAX->new( Handler => $self->{'xmlHandler'} );
eval { $parser->parse( Source => { SystemId => $full_path } ) };
if ($@) {
$logger->fatal("Invalid XML description: $@") if $self->{'instance'} ne '';
undef $self->{'system'};
return;
}
$self->{'system'} = $self->{'xmlHandler'}->get_system_hash;
}
$self->set_system_overrides;
$ENV{'PATH'} = '/bin:/usr/bin'; ## no critic (RequireLocalizedPunctuationVars) #so we don't foul taint check
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Make %ENV safer
$self->{'system'}->{'read_access'} ||= 'public'; #everyone can view by default
$self->{'system'}->{'host'} ||= $self->{'host'} || 'localhost';
$self->{'system'}->{'port'} ||= $self->{'port'} || 5432;
$self->{'system'}->{'user'} ||= $self->{'user'} || 'apache';
$self->{'system'}->{'password'} ||= $self->{'password'} || 'remote';
if ( ( $self->{'system'}->{'dbtype'} // '' ) eq 'isolates' ) {
$self->{'system'}->{'view'} ||= 'isolates';
$self->{'system'}->{'labelfield'} ||= 'isolate';
if ( !$self->{'xmlHandler'}->is_field( $self->{'system'}->{'labelfield'} ) ) {
$logger->error( "The defined labelfield '$self->{'system'}->{'labelfield'}' does not exist in the database. "
. "Please set the labelfield attribute in the system tag of the database XML file." );
}
}
$self->db_connect;
$self->setup_datastore;
$self->{'page_size'} =
( BIGSdb::Utils::is_int( param('page_size') ) && param('page_size') > 0 ) ? param('page_size') : PAGE_SIZE;
return;
};
#Drop the connection because we may have hundreds of databases on the system. Keeping them all open will exhaust resources.
#This could be made optional for systems with only a few databases.
hook after => sub {
my $self = setting('self');
$self->{'dataConnector'}->drop_connection( { host => $self->{'system'}->{'host'}, dbase_name => $self->{'system'}->{'db'} } );
};
#Get the contents of the rest_db database.
sub get_resources {
my ($self) = @_;
my $dbases =
$self->{'datastore'}->run_query( "SELECT * FROM resources ORDER BY name", undef, { fetch => 'all_arrayref', slice => {} } );
return $dbases;
}
1;
#Written by Keith Jolley
#Copyright (c) 2014, University of Oxford
#E-mail: keith.jolley@zoo.ox.ac.uk
#
#This file is part of Bacterial Isolate Genome Sequence Database (BIGSdb).
#
#BIGSdb 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.
#
#BIGSdb 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 BIGSdb. If not, see <http://www.gnu.org/licenses/>.
package BIGSdb::REST::Routes::Isolates;
use strict;
use warnings;
use 5.010;
use Dancer2 appname => 'BIGSdb::REST::Interface';
use Log::Log4perl qw(get_logger);
use BIGSdb::Utils;
my $logger = get_logger('BIGSdb.Rest');
#Isolate database routes
get '/db/:db/isolates' => sub {
my $self = setting('self');
my ($db) = params->{'db'};
my $page = ( BIGSdb::Utils::is_int( param('page') ) && param('page') > 0 ) ? param('page') : 1;
my $isolate_count = $self->{'datastore'}->run_query("SELECT COUNT(*) FROM $self->{'system'}->{'view'}");
my $pages = int( $isolate_count / $self->{'page_size'} ) + 1;
my $offset = ( $page - 1 ) * $self->{'page_size'};
my $ids =
$self->{'datastore'}->run_query( "SELECT id FROM $self->{'system'}->{'view'} ORDER BY id OFFSET $offset LIMIT $self->{'page_size'}",
undef, { fetch => 'col_arrayref' } );
my $values = [];
if (@$ids) {
my $paging = [];
if ( $page > 1 ) {
push @$paging, { first => request->uri_base . "/db/$db/isolates?page=1&page_size=$self->{'page_size'}" };
push @$paging, { previous => request->uri_base . "/db/$db/isolates?page=" . ( $page - 1 ) . "&page_size=$self->{'page_size'}" };
}
if ( $page < $pages ) {
push @$paging, { next => request->uri_base . "/db/$db/isolates?page=" . ( $page + 1 ) . "&page_size=$self->{'page_size'}" };
}
if ( $page != $pages ) {
push @$paging, { last => request->uri_base . "/db/$db/isolates?page=$pages&page_size=$self->{'page_size'}" };
}
push @$values, { paging => $paging };
my @links;
push @links, request->uri_for("/db/$db/isolates/$_")->as_string foreach @$ids;
push @$values, { isolates => \@links };
}
return $values;
};
get '/db/:db/isolates/:id' => sub {
my $self = setting('self');
my ( $db, $id ) = ( params->{'db'}, params->{'id'} );
if ( !BIGSdb::Utils::is_int($id) ) {
status(400);
return { status => 400, error => 'id must be an integer' };
}
my $values = [];
my $field_values =
$self->{'datastore'}->run_query( "SELECT * FROM $self->{'system'}->{'view'} WHERE id=?", $id, { fetch => 'row_hashref' } );
if ( !defined $field_values->{'id'} ) {
status(404);
return { status => 404, error => "Isolate $id does not exist" };
}
my $field_list = $self->{'xmlHandler'}->get_field_list;
foreach my $field (@$field_list) {
if ( $field eq 'sender' || $field eq 'curator' ) {
push @$values, { $field => request->uri_for("/db/$db/users/$field_values->{$field}")->as_string };
} else {
push @$values, { $field => $field_values->{ lc $field } } if defined $field_values->{ lc $field };
}
}
return $values;
};
1;
#Written by Keith Jolley
#Copyright (c) 2014, University of Oxford
#E-mail: keith.jolley@zoo.ox.ac.uk
#
#This file is part of Bacterial Isolate Genome Sequence Database (BIGSdb).
#
#BIGSdb 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.
#
#BIGSdb 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 BIGSdb. If not, see <http://www.gnu.org/licenses/>.
package BIGSdb::REST::Routes::Resources;
use strict;
use warnings;
use 5.010;
use Dancer2 appname => 'BIGSdb::REST::Interface';
use Log::Log4perl qw(get_logger);
my $logger = get_logger('BIGSdb.Rest');
#Resource description routes
get '/' => sub {
my $self = setting('self');
my $resources = $self->get_resources;
my $values = [];
foreach my $resource (@$resources) {
my $databases = [];
push @$databases,
{ name => 'Sequence/profile definitions', href => request->uri_for("/db/$resource->{'seqdef_config'}")->as_string }
if defined $resource->{'seqdef_config'};
push @$databases, { name => 'Isolates', href => request->uri_for("/db/$resource->{'isolates_config'}")->as_string }
if defined $resource->{'isolates_config'};
push @$values, { name => $resource->{'name'}, description => $resource->{'description'}, databases => $databases };
}
return $values;
};
get qr{^/db/?+$} => sub {
redirect '/';
};
get '/db/:db' => sub {
my $self = setting('self');
my $db = params->{'db'};
if ( !$self->{'system'}->{'db'} ) {
status(404);
return { status => 404, error => "Database '$db' does not exist" };
}
if ( $self->{'system'}->{'dbtype'} eq 'isolates' ) {
my $count = $self->{'datastore'}->run_query("SELECT COUNT(*) FROM $self->{'system'}->{'view'}");
return {
records => $count,
isolates => request->uri_for("/db/$db/isolates")->as_string,
fields => request->uri_for("/db/$db/fields")->as_string,
schemes => request->uri_for("/db/$db/schemes")->as_string,
loci => request->uri_for("/db/$db/loci")->as_string,
};
} elsif ( $self->{'system'}->{'dbtype'} eq 'sequences' ) {
} else {
return { title => 'Database configuration is invalid' };
}
};
1;
#Written by Keith Jolley
#Copyright (c) 2014, University of Oxford
#E-mail: keith.jolley@zoo.ox.ac.uk
#
#This file is part of Bacterial Isolate Genome Sequence Database (BIGSdb).
#
#BIGSdb 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.
#
#BIGSdb 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 BIGSdb. If not, see <http://www.gnu.org/licenses/>.
package BIGSdb::REST::Routes::Users;
use strict;
use warnings;
use 5.010;
use Dancer2 appname => 'BIGSdb::REST::Interface';
use Log::Log4perl qw(get_logger);
use BIGSdb::Utils;
my $logger = get_logger('BIGSdb.Rest');
#User routes
get '/db/:db/users/:user' => sub {
my $self = setting('self');
my $user_id = param('user');
if ( !BIGSdb::Utils::is_int($user_id) ) {
status(400);
return { status => 400, error => 'User id must be an integer' };
}
my $user = $self->{'datastore'}->run_query( "SELECT * FROM users WHERE id=?", $user_id, { fetch => 'row_hashref' } );
if ( !defined $user->{'id'} ) {
status(404);
return { status => 404, error => "User $user_id does not exist" };
}
my $values = [];
foreach my $field (qw(id first_name surname affiliation email)) {
#Only include E-mail for curators/admins
next if $field eq 'email' && !$self->{'system'}->{'privacy'} && $user->{'status'} eq 'user';
push @$values, { $field => $user->{$field} };
}
return $values;
};
1;
CREATE TABLE resources (
name text NOT NULL,
description text NOT NULL,
seqdef_config text,
isolates_config text,
PRIMARY KEY (name)
);
GRANT SELECT ON resources TO apache;
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment