Minion-Backend-API/lib/Mojolicious/Plugin/Minion/API.pm
package Mojolicious::Plugin::Minion::API;
use Mojo::Base 'Mojolicious::Plugin';
use List::Util qw/first/;
use Carp 'croak';
our $VERSION = 1.00;
sub register {
my ($self, $app, $config) = @_;
# minion required
$config->{minion} ||= eval { $app->minion } || croak 'A minion instance is required';
# set helper minion, if not defined
$app->helper('minion' => sub {
return $config->{minion};
}) unless $app->can('minion');
# set helper backend
$app->helper('backend' => sub {
return $config->{minion}->backend;
});
# save tasks
$app->hook(before_routes => sub {
my $c = shift;
if ($c->req->json && $c->req->json->{tasks}) {
$config->{minion}->tasks->{$_} = 1 for @{$c->req->json->{tasks}};
}
});
# enable all origin
$app->hook(before_render => sub {
my $c = shift;
if ($c->req->method ne 'OPTIONS') {
$c->res->headers->header('Access-Control-Allow-Origin' => '*');
}
});
# global
$app->routes->options('/:all' => [all => qr/.+/] => sub {
my $c = shift;
# headers
$c->res->headers->header('Access-Control-Allow-Origin' => '*');
$c->res->headers->header('Access-Control-Allow-Credentials' => 'true');
$c->res->headers->header('Access-Control-Allow-Methods' => 'OPTIONS, GET, POST, DELETE, PUT, PATCH');
$c->res->headers->header('Access-Control-Allow-Headers' => 'Content-Type, Authorization');
# return status 200 to options
$c->respond_to(any => {data => '', status => 200});
});
# router api
my $pattern = $config->{pattern} ? $config->{pattern} : '/';
$pattern = '/' . $pattern unless $pattern =~ /^\//;
my $api = $app->routes->under($pattern => sub {
my $c = shift;
# check authentication
if ($config->{authentication}) {
return 1 if $c->req->url->to_abs->userinfo eq $config->{authentication};
$c->res->headers->www_authenticate('Basic');
$c->render(text => 'Authentication required!', status => 401);
return;
}
# valid ips enabled
if ($config->{ips_enabled}) {
my $ip = $c->tx->remote_address || $c->tx->original_remote_address;
unless (first { $ip eq $_ } @{$config->{ips_enabled}}) {
$c->render(text => 'This IP is not allowed!', status => 404);
return;
}
}
return 1;
});
# broadcast
$api->put('/broadcast' => sub {
my $c = shift;
my $command = $c->req->json->{command};
my $args = $c->req->json->{args};
my $ids = $c->req->json->{ids};
&_render($c, $app->backend->broadcast($command, $args, $ids));
});
# dequeue
$api->post('/dequeue' => sub {
my $c = shift;
my $id = $c->req->json->{id};
my $wait = $c->req->json->{wait};
my $options = $c->req->json->{options};
&_render($c, $app->backend->dequeue($id, $wait, $options));
});
# enqueue
$api->post('/enqueue' => sub {
my $c = shift;
my $task = $c->req->json->{task};
my $args = $c->req->json->{args};
my $options = $c->req->json->{options};
&_render($c, $app->backend->enqueue($task, $args, $options));
});
# fail_job
$api->patch('/fail-job' => sub {
my $c = shift;
my $id = $c->req->json->{id};
my $retries = $c->req->json->{retries};
my $result = $c->req->json->{result};
&_render($c, $app->backend->fail_job($id, $retries, $result));
});
# finish_job
$api->patch('/finish-job' => sub {
my $c = shift;
my $id = $c->req->json->{id};
my $retries = $c->req->json->{retries};
my $result = $c->req->json->{result};
&_render($c, $app->backend->finish_job($id, $retries, $result));
});
# history
$api->get('/history' => sub {
my $c = shift;
&_render($c, $app->backend->history);
});
# list_jobs
$api->get('/list-jobs' => sub {
my $c = shift;
my $offset = $c->req->json->{offset};
my $limit = $c->req->json->{limit};
my $options = $c->req->json->{options};
&_render($c, $app->backend->list_jobs($offset, $limit, $options));
});
# list_locks
$api->get('/list-locks' => sub {
my $c = shift;
my $offset = $c->req->json->{offset};
my $limit = $c->req->json->{limit};
my $options = $c->req->json->{options};
&_render($c, $app->backend->list_locks($offset, $limit, $options));
});
# list_workers
$api->get('/list-workers' => sub {
my $c = shift;
my $offset = $c->req->json->{offset};
my $limit = $c->req->json->{limit};
my $options = $c->req->json->{options};
&_render($c, $app->backend->list_workers($offset, $limit, $options));
});
# lock
$api->get('/lock' => sub {
my $c = shift;
my $name = $c->req->json->{name};
my $duration = $c->req->json->{duration};
my $options = $c->req->json->{options};
&_render($c, $app->backend->lock($name, $duration, $options));
});
# note
$api->patch('/note' => sub {
my $c = shift;
my $id = $c->req->json->{id};
my $merge = $c->req->json->{merge};
&_render($c, $app->backend->note($id, $merge));
});
# receive
$api->patch('/receive' => sub {
my $c = shift;
my $id = $c->req->json->{id};
&_render($c, $app->backend->receive($id));
});
# register_worker
$api->post('/register-worker' => sub {
my $c = shift;
my $id = $c->req->json->{id};
my $options = $c->req->json->{options};
&_render($c, $app->backend->register_worker($id, $options));
});
# remove_job
$api->delete('/remove-job' => sub {
my $c = shift;
my $id = $c->req->json->{id};
&_render($c, $app->backend->remove_job($id));
});
# repair
$api->post('/repair' => sub {
my $c = shift;
&_render($c, $app->backend->repair);
});
# reset
$api->post('/reset' => sub {
my $c = shift;
my $options = $c->req->json->{options};
&_render($c, $app->backend->reset($options));
});
# retry_job
$api->put('/retry-job' => sub {
my $c = shift;
my $id = $c->req->json->{id};
my $retries = $c->req->json->{retries};
my $options = $c->req->json->{options};
&_render($c, $app->backend->retry_job($id, $retries, $options));
});
# stats
$api->get('/stats' => sub {
my $c = shift;
&_render($c, $app->backend->stats);
});
# unlock
$api->delete('/unlock' => sub {
my $c = shift;
my $name = $c->req->json->{name};
&_render($c, $app->backend->unlock($name));
});
# unregister_worker
$api->delete('/unregister-worker' => sub {
my $c = shift;
my $id = $c->req->json->{id};
&_render($c, $app->backend->unregister_worker($id));
});
}
sub _render {
my ($c, $result) = @_;
$c->render(
json => {
success => 1,
result => $result || ''
}
);
}
1;
=encoding utf8
=head1 NAME
Mojolicious::Plugin::Minion::API - Plugin to receive requests from Minion::Backend::API
=head1 SYNOPSIS
use Mojolicious::Lite;
use Minion;
plugin 'Minion::API' => {
minion => Minion->new(Pg => 'postgresql://postgres@/test'),
authentication => 'user:pass',
ips_enabled => [
'127.0.0.1',
'172.16.0.1',
'192.168.0.1'
]
};
app->start;
=head1 DESCRIPTION
L<Mojolicious::Plugin::Minion::API> is a plugin L<Mojolicious>.
This module provides an API to receive request from L<Minion::Backend::API>
=head1 OPTIONS
L<Mojolicious::Plugin::Minion::API> supports the following options.
=head2 minion
# Mojolicious::Lite
plugin 'Minion' => {
mysql => 'mysql://user@127.0.0.1/minion_jobs'
};
plugin 'Minion::API' => {
minion => app->minion
};
L<Minion> object to handle backend, this option is mandatory.
=head2 pattern
# Mojolicious::Lite
plugin 'Minion::API' => {
pattern => '/minion-api' # https://my-api.com/minion-api
};
This option is to set pattern in url, see more L<Mojolicious::Routes::Route#under>
=head2 authentication
# Mojolicious::Lite
plugin 'Minion::API' => {
authentication => 'user:pass'
};
This options is to the security of your application, adding a basic authentication.
=head2 ips_enabled
# Mojolicious::Lite
plugin 'Minion::API' => {
ips_enabled => [
'127.0.0.1',
'172.16.0.1',
'192.168.0.1'
]
};
This options is to the security of your application, validating ips enabled.
=head1 SEE ALSO
L<Minion::Backend::API>, L<Minion>, L<Mojolicious::Guides>, L<https://mojolicious.org>.
=head1 AUTHOR
Lucas Tiago de Moraes C<lucastiagodemoraes@gmail.com>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2020 by Lucas Tiago de Moraes.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
=cut