package Strehler::API;
$Strehler::API::VERSION = '1.6.8';
use strict;
use Dancer2 0.166001;
use Dancer2::Serializer::JSON;
use Strehler::Helpers;
use Strehler::Meta::Category;
prefix '/api/v1';
set layout => undef;
my $root_path = __FILE__;
$root_path =~ s/API\.pm//;
set views => $root_path . 'views';
get '/reference' => sub {
my @entities = Strehler::Helpers::entities_list();
my @single_calls;
my @plural_calls;
foreach my $e (@entities)
{
my $class = Strehler::Helpers::class_from_entity($e);
next if ! $class->exposed();
push @single_calls, '/api/v1/' . $e . '/<id>';
push @single_calls, '/api/v1/' . $e . '/slug/<slug>';
push @plural_calls, '/api/v1/' . $class->plural() . '/';
push @plural_calls, '/api/v1/' . $class->plural() . '/<category>/<subcategory>/';
}
template "api/reference", { page_title => "API reference", single_calls => \@single_calls, plural_calls => \@plural_calls }, { layout => 'light-admin' };
};
get '/:entity/slug/:slug' => sub {
my $entity = params->{entity};
my $slug = params->{'slug'};
my $callback = params->{'callback'} || undef;
my $lang = params->{'lang'} || config->{'Strehler'}->{'default_language'};
my $class = Strehler::Helpers::class_from_entity($entity);
return pass if ! $class;
return pass if ! $class->exposed();
return pass if ! $class->slugged();
my $obj = $class->get_by_slug($slug, $lang);
if(! $obj)
{
return error_handler(404, "Element doesn't exists");
}
if($obj->publishable() && ! $obj->get_attr('published'))
{
return error_handler(404, "Element doesn't exists");
}
my %data = $obj->get_json_data($lang);
if($callback)
{
content_type('application/javascript');
}
else
{
content_type('application/json');
}
return serialize(\%data, $callback);
};
get '/:entity/schema' => sub {
my $entity = params->{entity};
my $callback = params->{'callback'} || undef;
my $class = Strehler::Helpers::class_from_entity($entity);
return pass if ! $class;
my @basic = $class->data_fields();
@basic = grep { $_ ne 'published' } @basic;
my @multilanguage = $class->multilang_data_fields();
my %data = ( 'basic' => \@basic,
'multilanguage' => \@multilanguage );
if($callback)
{
content_type('application/javascript');
}
else
{
content_type('application/json');
}
return serialize(\%data, $callback);
};
get '/:entity/:id' => sub {
my $entity = params->{entity};
my $id = params->{'id'};
my $callback = params->{'callback'} || undef;
my $lang = params->{'lang'} || config->{'Strehler'}->{'default_language'};
my $class = Strehler::Helpers::class_from_entity($entity);
return pass if ! $class;
return pass if ! $class->exposed();
my $obj = $class->new($id);
if(! $obj->exists())
{
return error_handler(404, "Element doesn't exists");
}
if($obj->publishable() && ! $obj->get_attr('published'))
{
return error_handler(404, "Element doesn't exists");
}
my %data = $obj->get_json_data($lang);
if($callback)
{
content_type('application/javascript');
}
else
{
content_type('application/json');
}
return serialize(\%data, $callback);
};
get '/**/' => sub {
my ($attributes) = splat;
my $entities = shift @{$attributes};
my $category;
if(@{$attributes})
{
$category = join('/', @{$attributes});
}
else
{
$category = undef;
}
my $callback = params->{'callback'} || undef;
my $lang = params->{'lang'} || config->{'Strehler'}->{'default_language'};
my $order = params->{'order'};
my $order_by = params->{'order_by'};
my $page = params->{'page'};
my $entries_per_page = params->{'entries_per_page'} || 20;
my $class = Strehler::Helpers::class_from_plural($entities);
return pass if ! $class;
return pass if ! $class->exposed();
my $category_id = undef;
my $ancestor = undef;
if($category)
{
my $category_obj = Strehler::Meta::Category->explode_name($category);
if(! $category_obj->exists())
{
return error_handler(404, "Category doesn't exists");
}
$category_id = $category_obj->get_attr('id');
}
my $parameters = {
order => $order,
order_by => $order_by,
language => $lang,
entries_per_page => $entries_per_page,
page => $page,
json => 1,
published => 1,
category_id => undef,
ancestor => $category_id
};
my $data = $class->get_list($parameters);
if($callback)
{
content_type('application/javascript');
}
else
{
content_type('application/json');
}
return serialize($data, $callback);
};
sub error_handler
{
my $code = shift || 500;
my $message = shift || "Generic error";
my $callback = shift || undef;
my $error_content = { error => $code, message => $message };
my $serializer = Dancer2::Serializer::JSON->new();
my $serialized = $serializer->serialize($error_content);
if($callback)
{
content_type('application/javascript');
$serialized = $callback . '( '. $serialized . ')';
}
else
{
content_type('application/json');
}
send_error($message, $code);
return $serialized;
}
sub serialize
{
my $content = shift;
my $callback = shift;;
my $serializer = Dancer2::Serializer::JSON->new();
my $serialized = $serializer->serialize($content);
if($callback)
{
$serialized = $callback . '( '. $serialized . ')';
}
return $serialized;
}
=encoding utf8
=head1 NAME
Strehler::API - App that gives a RESTful interface to Strehler data
=head1 DESCRIPTION
Strehler::API is an out-of-the-box API system designed to give back contents created with Strehler backend in a JSON (or JSONp) shape.
It's main purpose is to make Strehler a complete server for client-side applications designed using advanced javascript, as L<Angular.js|https://angularjs.org/>.
Articles and Images are exposed through API by default, all the other custom entities can be exposed as well. Exposed flag was created for that purpose.
All the API calls are under B</api/v1/>
=head1 API REFERENCE
Strehler::API are read-only API so there're just two calls you can do, both GET, to obtain data.
=over 4
=item /api/v1/$entity/$entity_id
This API just return all the data related to entity $entity with id $entity_id.
Data is always the extended data from get_ext_data in L<Strehler::Element>. You can pass to the call B<lang> as parameter to obtain data in a certain language. If no lang parameter is provided, data is returned using the default language.
If the entity is publishable, only data from published articles is returned. Calling for an unpublished article return 404.
B<Example>: /api/v1/article/15
=item /api/v1/$entity/slug/$slug
As the previous API, using article slug instead of ID. It works only with slugged entities, entities that has L<Strehler::Element::Role:Slugged> as L<Strehler::Element::Article>.
=item /api/v1/$plural_entity/$category/$subcategory/
This API returns in a JSON format a call to get_list sub from L<Strehler::Element>. So data structure is:
{
page => 1, #page retrieved
last_page => 3, #maximum callable page number
to_view => $objects #list of objects, all returned with their extended data.
}
$plural_entity is the plural name of the entity. You can let Strehler derives it by itself from entity name using L<Lingua::EN::Inflect> or you can configure it in the config.yml or in the class as all other attributes.
$category and $subcategory are optional. If you call the API using them you'll retrieve all the elements under $category/$subcategory category. Pay attention that if you just call /api/v1/$plural_entity/$category/ system will return the objects under $category and all the objects under $category's subcategories.
API output is controlled by many parameters (a subset of get_list available parameters):
lang: output language, as for single item API
order, order_by: to change the way elements are ordered
page: to control pagination
entries_per_page: to say how many elements display every page
B<Example>: /api/v1/articles/foo/bar/
=item /api/v1/reference
Returns a web page where all the available APIs are listed. Automatically generated.
=back
=head2 JSONP CALLBACK
Adding B<callback> parameter to any API, return format will be JSONp instead of JSON.
=cut
1;