The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package WebService::CloudFlare::Host;
use Moose;
use Try::Tiny;
use Data::Dumper;
use LWP;

our $VERSION = "000100"; # 0.1.0
$VERSION = eval $VERSION;

has 'host_key' => (
    is          => 'rw',
    isa         => 'Str',
    required    => 1,
);

has 'base_api'  => (
    is          => 'ro',
    isa         => 'Str',
    default     => 'https://api.cloudflare.com/host-gw.html',
);

has 'user_agent' => (
    is          => 'ro',
    isa         => 'Str',
    default     => 'WebService::CloudFlare::Host/1.0',
);

has 'http_timeout' => (
    is          => 'ro',
    isa         => 'Int',
    default     => 60,
);

has 'ua'        => (
    is          => 'ro',
    isa         => 'LWP::UserAgent',
    lazy_build  => 1,
);

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

    # Load the request class.
    try {
        Class::MOP::load_class("WebService::CloudFlare::Host::Request::$class");
    } catch {
        $self->_throw_exception( $_, "Loading request class: $class", 
            "Class::MOP::load_class", 
            "WebService::CloudFlare::Host::Request::$class"
        );
    };

    # Create the request object.
    my $req = try {
        "WebService::CloudFlare::Host::Request::$class"->new( %args )
    } catch {
        $self->_throw_exception( $_, 'Creating API Request',
            "WebService::CloudFlare::Host::Request::$class", Dumper \%args
        );
    };

    # Make the actual HTTP request.
    my $http_res = try {
        $self->_do_call( $req );
    } catch {
        $self->_throw_exception( $_, 'Making HTTP Call To Server API',
            'WebService::CloudFlare::Host::do_call', Dumper $req
        );
    };

    # Create a response object to send back to the user.
    my $res = try {
        $self->_create_response( $class, $http_res );
    } catch {
        $self->_throw_exception( $_, 'Creating API Response From HTTP Data',
            'WebService::CloudFlare::Host::create_response', Dumper $http_res,
        );
    };
    return $res;
}

sub _throw_exception {
    my ( $self, $message, $layer, $function, $args ) = @_;

    # We installed ::Exception with the package, it's here.
    Class::MOP::load_class('WebService::CloudFlare::Host::Exception');

    # Let's get to the bottom of the exception...
    my $exception;
    while ( $message->isa( 'WebService::CloudFlare::Host::Exception' ) ) {
        $exception = $message;
        $message = $message->message;
    }
    die $exception if $exception;


    die WebService::CloudFlare::Host::Exception->new( 
        message  => $message,
        layer    => $layer,
        function => $function,
        args     => $args,
    );
}

sub _do_call {
    my ( $self, $request ) = @_;
    
    my %arguments = $request->as_post_params;
    $arguments{host_key} = $self->host_key;

    return $self->ua->post($self->base_api, \%arguments);
}

sub _create_response {
    my ( $self, $class, $http ) = @_;
    
    try {
        Class::MOP::load_class("WebService::CloudFlare::Host::Response::$class");
    } catch {
        $self->_throw_exception( $_, "Loading request class: $class", 
            "Class::MOP::load_class", 
            "WebService::CloudFlare::Host::Reponse::$class"
        );
    };

    return "WebService::CloudFlare::Host::Response::$class"->new( $http );

}

sub _build_ua {
    my ( $self ) = @_;
    return LWP::UserAgent->new(
        timeout => $self->http_timeout,
        user_agent => $self->user_agent
    );
}

1;

=head1 NAME

WebService::CloudFlare::Host - A client API For Hosting Partners 

=head1 VERSION

000100 (0.1.0)

=head1 SYNOPSIS

    my $CloudFlare = WebService::CloudFlare::Host->new(
        host_key => 'cloudflare hostkey',
        timeout  => 30,
    );

    my $response = eval { $CloudFlare->call('UserCreate',
        email => 'richard.castle@hyperionbooks.com',
        pass  => 'ttekceBetaK',
    ) };

    if ( $@ ) {
        die "Error: in " . $@->function . ": " . $@->message;
    }


    printf("Got API Keys: User Key: %s, User API Key: %s",
        $response->user_key, $response->api_key
    );

=head1 DESCRIPTION

WebService::CloudFlare::Host is a client side API library
to make using CloudFlare simple for hosting providers.

It gives a simple interface for making API calls, getting
response objects, and implementing additional API calls.

All API calls have a Request and Response object that define
the accepted information for that call.

=head1 METHODS

The only method used is C<call($api_call, %arguments)>.

When making an API call, the first argument defines the API request
to load.  This is loaded from Request::.  Additional arguments are
passed as-is to the Request Object.

Once the object has been made, an HTTP call to the CloudFlare API
is made.  The JSON returned is used to construct a Response object
loaded from Response:: with the same name as the Request object.

C<call> dies on error, giving a WebService::CloudFlare::Host::Exception
object and should be run in an eval or with Try::Tiny.

=head1 STANDARD OBJECT METHODS

=head2 Standard Request Object

The host key is dynamically inserted into the Requests.

=head2 Standard Response Object 

The following methods are avilable on standard Response objects.

=over 4

=item result

The result sent from the API: 'success' or 'error'.

=item message

If the result is 'error', a message will be set with a user-readable explaination
of the error; otherwise, this method will not exist.

=item code

If the result is 'error', a code will be set.  This error can be found at
L<http://www.cloudflare.com/docs/host-api.html>.

=back

=head1 API CALLS


=head2 UserCreate

The UserCreate API call creates a user for the CloudFlare
service, as if they had signed up through CloudFlare's website.

my $response = eval { $CloudFlare->call('UserCreate',
    email => 'Casey.Klein@partydown.com',
    pass  => 'omgPassword',
    unique_id => '506172747920446f776e203c33205521',
)};

=head3 Request

The request uses the following parameters:

=over 4

=item email 

The email address that the end-user can use to sign into
the CloudFlare service.

=item pass

The password the user can use to sign into the CloudFlare service.
This should not be recorded on the Hosting Provider's side.

=item user

A username for the user.  This is used in saluations and emails from
CloudFlare.  It has no bearing in the rest of the API.

=item unique_id

A unique id that may be used for UserLookup calls (as opposed to the 
user's email address).

=item clobber

When set to 1, a user's unique_id can be replaced with a new unique_id.

=back 

=head3 Response

printf("Created account for %s, with Unique ID => %s, "
    . "User Key => %s, and API Key => %s",
    $response->unique_id, $response->user_key, $response->api_key
    );

The response sets the following methods:

=over 4

=item api_key

This API key allows a hosting provider to act as the user.  All user
API requests can be completed with this key.

=item email

This is the registered email account for the CloudFlare user.

=item user_key

This user_key is used to make Hosting API calls, specifically
the ZoneSet, ZoneDelete, and ZoneLookup API calls. 

=item unique_id

This can be used instead of the email address to do UserLookup calls.

=item username

The username.

=back

=head2 UserAuth

The UserAuth API call gives the hosting provider access to
the User's account.  The call returns a user_key as well as
the api_key and authenticates the Hosting Provider to perform
actions as the user.

=head3 Request

my $response = eval { $CloudFlare->call('UserAuth',
    email => 'Casey.Klein@partydown.com',
    pass  => 'omgPassword',
) };

The request uses the following parameters:

=over 4

=item email

The email address that the user used to register the account
with CloudFlare.

=item pass

The password the user uses to login to CloudFlare.  This should
not be stored on a hosting provider's side.

=item unique_id

A unique_id that may be used to perform UserLookup API calls.

=item clobber

If true, the unique_id can be clobbered.

=back 

=head3 Response

The response sets the following methods:

=over 4

=item api_key

This API key allows a hosting provider to act as the user.  All user
API requests can be completed with this key.

=item email

This is the registered email account for the CloudFlare user.

=item user_key

This user_key is used to make Hosting API calls, specifically
the ZoneSet, ZoneDelete, and ZoneLookup API calls. 

=item unique_id

This can be used instead of the email address to do UserLookup calls.

=back

=head2 UserLookup

The UserLookup API call gives a hosting provider the ablity
to find information on a user account that it has access to through
either the unique_id or the email address that was used in UserAuth
or UserCreate API calls.

=head3 Request

my $response = eval { $CloudFlare->call('UserLookup',
    email => 'Casey.Klein@partydown.com',
) };

The request uses the following parameters:

=over 4

=item email

The email address that was used in UserCreate or UserAuth
API call.  This is required if C<unique_id> is not set.

=item unique_id

The unique_id that was last set in UserCreate or UserAuth
API call.  This is required if C<email> is not set.

=back 

=head3 Response

The response sets the following methods:

=over 4

=item api_key

This API key allows a hosting provider to act as the user.  All user
API requests can be completed with this key.

=item email

This is the registered email account for the CloudFlare user.

=item user_key

This user_key is used to make Hosting API calls, specifically
the ZoneSet, ZoneDelete, and ZoneLookup API calls. 

=item unique_id

This can be used instead of the email address to do UserLookup calls.

=item user_authed

True if the hosting provider has access to this user.

=item user_exists

True if the user exists in the CloudFlare system.

=item zones

A list of zones that the user has associated with his or her account.

=back

=head2 ZoneSet

This associates a zone with the CloudFlare service for the
user whose user_key is used.

=head3 Request

my $response = eval { $CloudFlare->call('ZoneSet',
    user_key    => 'e7af5f120e3240e7bfba063b5f62c922',
    resolve_to  => '173.230.133.102',
    zone_name   => 'partydown.com',
    subdomains  => 'www',
) };

The request uses the following parameters:

=over 4

=item user_key

The user_key provided by UserCreate, UserLookup, or UserAuth.

The zone will be associated with the user whose user_key is used.

=item resolve_to

The IP address or a CNAME that resolves to the origin server that
hosts the content for the given website.

=item zone_name

The name of the domain.

=item subdomains

A comma-seperated list of the subdomains from the zone for which
CloudFlare should act as a reverse proxy.

=back 

=head3 Response

The response sets the following methods:

=over 4

=item zone_name

The name of the domain.

=item resolving

The origin server that has been recorded.  The same as the one
submitted in the Request.

=item forwarded

A hashref whose keys are the domain, and whose value is the CNAME that
should be used in the DNS system to have the requests be processed by
CloudFlare.

=item hosted

A hashref whose keys are the the domain name(s) that are hosted, and
whose value is the resolving address.

=back

=head2 ZoneDelete

This will remove a zone from being hosted by the user whose
user_key is used, provided they are the ones hosting the zone.

=head3 Request

my $response = eval { $CloudFlare->call('ZoneDelete',
    user_key    => 'e7af5f120e3240e7bfba063b5f62c922',
    zone_name   => 'partydown.com',
) };

The request uses the following parameters:

=over 4

=item user_key

The user_key of the user whose zone is being removed from CloudFlare.

=item zone_name

The name of the zone to be removed.

=back 

=head3 Response

The response sets the following methods:

=over 4

=item zone_name

The name of the zone from the Request.

=item zone_deleted

True if the zone was deleted.

=back

=head2 ZoneLookup

Find information on a zone hosted by a given user_key.

=head3 Request

my $response = eval { $CloudFlare->call('ZoneLookup',
    user_key    => 'e7af5f120e3240e7bfba063b5f62c922',
    zone_name   => 'partydown.com',
) };

The request uses the following parameters:

=over 4

=item user_key

The user_key of the user whose zone is being removed from CloudFlare.

=item zone_name

The name of the zone to be removed.

=back 

=head3 Response

The response sets the following methods:

=over 4

=item zone_name

=item resolving

The origin server that has been recorded.  The same as the one
submitted in the Request.

=item forwarded

A hashref whose keys are the domain, and whose value is the CNAME that
should be used in the DNS system to have the requests be processed by
CloudFlare.

=item hosted

A hashref whose keys are the the domain name(s) that are hosted, and
whose value is the resolving address.

=item zone_exists

True if the zone exists in the CloudFlare service.

=item zone_hosted

True if the zone is hosted by this user_key.

=back

=head1 CREATING API CALLS

TBD

=head2 Request Classes

=head2 Response Classes

=head1 AUTHOR

SymKat I<E<lt>symkat@symkat.comE<gt>>

=head1 COPYRIGHT AND LICENSE

This is free software licensed under a I<BSD-Style> License.  Please see the 
LICENSE file included in this package for more detailed information.

=head1 AVAILABILITY

The latest version of this software is available through GitHub at
https://github.com/symkat/webservice-cloudflare-host/

=cut