Group
Extension

PAUSE-Permissions-MetaCPAN/lib/PAUSE/Permissions/MetaCPAN.pm

package PAUSE::Permissions::MetaCPAN 0.100;
use v5.16;
use warnings;

use Carp ();
use HTTP::Tiny 0.055;
use IO::Socket::SSL 1.42;
use JSON::PP ();

sub new {
    my ($class, %args) = @_;
    my $http = $args{http} || do {
        my $agent = sprintf "%s/%s", $class =~ s/::/-/gr, $class->VERSION;
        HTTP::Tiny->new(verify_SSL => 1, agent => $agent);
    };
    my $url = $args{url} || "https://fastapi.metacpan.org/v1/permission/_search";
    bless { http => $http, url => $url }, $class;
}

sub get {
    my ($self, %args) = @_;

    Carp::croak "either author or modules is required" if !$args{author} && !$args{modules};

    if (my $author = $args{author}) {
        my $hit = $self->_query(%args);
        my %hit = (owner => [], co_maintainer => []);
        for my $module (@$hit) {
            if ($module->{owner} eq $author) {
                push @{$hit{owner}}, $module;
            } else {
                push @{$hit{co_maintainer}}, $module;
            }
        }
        return \%hit;
    }

    my @hit;
    my @module = @{$args{modules}}; # must copy
    # elasticsearch may return "too_many_clauses: maxClauseCount is set to 1024"
    while (my @m = splice @module, 0, 1024) {
        my $hit = $self->_query(modules => \@m);
        push @hit, @$hit;
    }
    my %hit;
    for my $module (@{$args{modules}}) {
        my ($found) = grep { $_->{module_name} eq $module} @hit;
        $hit{$module} = $found;
    }
    return \%hit;
}

sub _query {
    my ($self, %args) = @_;

    my %bool;
    if (my $author = $args{author}) {
        $bool{should} = [
            { term => { owner => $author } },
            { term => { co_maintainers => $author } },
        ];
        $bool{minimum_should_match} = 1;
    } elsif (my $modules = $args{modules}) {
        $bool{should} = [
            map +{ term => { module_name => $_ } }, @$modules
        ];
        $bool{minimum_should_match} = 1;
    }

    my $from = 0;
    my $times = 0;
    my @hit;
    while (1) {
        $times++;
        Carp::croak "too many request for $self->{url}" if $times > 6;
        my $payload = {
            query => { bool => \%bool },
            sort => [ { module_name => 'asc' } ],
            size => 2000,
            from => $from,
        };
        my $body = JSON::PP::encode_json $payload;
        my $res = $self->{http}->post($self->{url}, {
            'content-type' => 'application/json',
            'content-length' => length $body,
            content => $body,
        });
        if ($res->{status} == 404) {
            last;
        } elsif (!$res->{success}) {
            Carp::croak "$res->{status} $res->{reason}, $self->{url}\n$res->{content}";
        }
        my $json = JSON::PP::decode_json $res->{content};
        my $total = $json->{hits}{total};
        push @hit, map $_->{_source}, @{$json->{hits}{hits}};
        last if @hit >= $total;
        $from = @hit;
    }
    \@hit;
}

1;
__END__

=encoding utf-8

=head1 NAME

PAUSE::Permissions::MetaCPAN - get module permissions from MetaCPAN API

=head1 SYNOPSIS

  use PAUSE::Permissions::MetaCPAN;

  my $api = PAUSE::Permissions::MetaCPAN->new;

  my $perm = $api->get(author => 'SKAJI');
  # {
  #   co_maintainer => [
  #     {
  #       co_maintainers => ["MIYAGAWA", "SKAJI", "SLANNING", "SYOHEX"],
  #       module_name => "Minilla",
  #       owner => "TOKUHIROM",
  #     },
  #     ...
  #   ],
  #   owner => [
  #     {
  #       co_maintainers => [],
  #       module_name => "Acme::RandomEmoji",
  #       owner => "SKAJI",
  #     },
  #     ...
  #   ],
  # }

  my $perm = $api->get(modules => ['LWP', 'NotFound', 'Plack']);
  # {
  #   LWP => {
  #     co_maintainers => ["ETHER", "GAAS", "MSCHILLI", "MSTROUT", "OALDERS"],
  #     module_name => "LWP",
  #     owner => "LWWWP",
  #   },
  #   NotFound => undef,
  #   Plack => {
  #     co_maintainers => [],
  #     module_name => "Plack",
  #     owner => "MIYAGAWA",
  #   },
  # }

=head1 DESCRIPTION

PAUSE::Permissions::MetaCPAN gets module permissions from MetaCPAN API.

=head1 METHOD

=head2 new

  my $api = PAUSE::Permissions::MetaCPAN->new;

Constructor. It optionally takes the following argument:

=over 4

=item http

HTTP::Tiny object. Default is

  HTTP::Tiny->new(verify_SSL => 1, agent => 'PAUSE-Permissions-MetaCPAN/VERSION')

=item url

MetaCPAN API url. Default is L<https://fastapi.metacpan.org/v1/permission/_search>.

=back

=head2 get

  my $perm = $api->get(author => 'AUTHOR');
  my $perm = $api->get(modules => ['Module1', 'Module2', ...]);

Get module permissions from MetaCPAN API. It must be called with either
C<author> or C<modules> argument.
It returns a hash reference that contains module permissions.

=head1 SEE ALSO

=over 4

=item L<PAUSE::Permissions>

=item L<https://fastapi.metacpan.org/>

=back

=head1 AUTHOR

Shoichi Kaji <skaji@cpan.org>

=head1 COPYRIGHT AND LICENSE

Copyright 2018 Shoichi Kaji <skaji@cpan.org>

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.

=cut


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.