Group
Extension

Business-Fixflo/lib/Business/Fixflo/Client.pm

package Business::Fixflo::Client;

=head1 NAME

Business::Fixflo::Client

=head1 DESCRIPTION

This is a class for the lower level requests to the fixflo API. generally
there is nothing you should be doing with this.

=cut

use strict;
use warnings;

use Moo;
with 'Business::Fixflo::Utils';
with 'Business::Fixflo::Version';

use Business::Fixflo::Exception;
use Business::Fixflo::Paginator;
use Business::Fixflo::Issue;
use Business::Fixflo::IssueDraft;
use Business::Fixflo::IssueDraftMedia;
use Business::Fixflo::Landlord;
use Business::Fixflo::LandlordProperty;
use Business::Fixflo::Agency;
use Business::Fixflo::Property;
use Business::Fixflo::PropertyAddress;
use Business::Fixflo::QuickViewPanel;

use MIME::Base64 qw/ encode_base64 /;
use LWP::UserAgent;
use JSON ();
use Carp qw/ carp confess /;

=head1 ATTRIBUTES

=head2 username

Your Fixflo username (required if api_key not supplied)

=head2 password

Your Fixflo password (required if api_key not supplied)

=head2 api_key

Your Fixflo API Key (required if username and password not supplied)

=head2 custom_domain

Your Fixflo custom domain

=head2 user_agent

The user agent string used in requests to the Fixflo API, defaults to
business-fixflo/perl/v . $version_of_this_library.

=head2 url_suffix

The url suffix to use after the custom domain, defaults to fixflo.com

=head2 base_url

The full url to use in calling the Fixflo API, defaults to:

    value of $ENV{FIXFLO_URL}
    or https:// $self->custom_domain . $self->url_suffix

=head2 api_path

The version of the Fixflo API to use, defaults to:

    /api/$Business::Fixflo::API_VERSION

=head2 ua_proxy_settings 

The custom proxy settings for the user agent class
The format is identical to LWP::UserAgent::proxy

    my $ff = Business::Fixflo->new( ... );

    $ff->client->ua_proxy_settings(
        [
            ftp => 'http://ftp.example.com:8001/',
	    [ 'http', 'https' ] => 'http://http.example.com:8001/',
	]
    );



=cut

has [ qw/ custom_domain / ] => (
    is       => 'ro',
    required => 1,
);

has [ qw/ username password api_key / ] => (
    is       => 'ro',
    required => 0,
);

has user_agent => (
    is      => 'ro',
    default => sub {
        require Business::Fixflo;
        # probably want more info in here, version of perl, platform, and such
        return "business-fixflo/perl/v" . $Business::Fixflo::VERSION;
    }
);

has url_suffix => (
    is       => 'ro',
    required => 0,
    default  => sub { 'fixflo.com' },
);

has url_scheme => (
    is       => 'ro',
    required => 0,
    default  => sub { 'https' },
);

has 'base_url' => (
    is       => 'ro',
    required => 0,
    lazy     => 1,
    default  => sub {
        my ( $self ) = @_;
        return $ENV{FIXFLO_URL}
            ? $ENV{FIXFLO_URL}
            : $self->url_scheme . '://' . $self->custom_domain . '.' . $self->url_suffix;
    }
);

has api_path => (
    is       => 'ro',
    required => 0,
    default  => sub { '/api/' . $Business::Fixflo::API_VERSION },
);

has ua_proxy_settings => (
    is       => 'rw',
    isa => sub {
        confess( "$_[0] is not an ARRAY ref" )
            if ref $_[0] ne 'ARRAY';
    },
    required => 0,
    default  => sub { [] },
);


sub BUILD {
    my ( $self ) = @_;

    if (
        ! $self->api_key
        && !( $self->username && $self->password )
    ) {
        confess( "api_key or username + password required" );
    }
}

sub _get_issues {
    my ( $self,$params ) = @_;

    return $self->_get_paginator_items(
		$params,'Issues','Business::Fixflo::Issue',
	);
}

sub _get_paginator_items {
    my ( $self,$params,$uri,$class ) = @_;

    my $items = $self->_api_request( 'GET',$uri,$params );

    my $Paginator = Business::Fixflo::Paginator->new(
        total_items => $items->{TotalItems} // undef,
        total_pages => $items->{TotalPages} // undef,
        links  => {
            next     => $items->{NextURL},
            previous => $items->{PreviousURL},
        },
        client  => $self,
        class   => $class,
        objects => [ map { $class->new(
            client => $self,
            ( $uri eq 'Property/Search'
				? ( Address => delete( $_->{Address} ),%{ $_ } )
				: $uri =~ /Landlords|PropertyAddress/
					? ( %{ $_ } ) 
					: ( url => $_ ),
			),
        ) } @{ $items->{Items} } ],
    );

    return $Paginator;
}

sub _get_agencies {
    my ( $self,$params ) = @_;

    return $self->_get_paginator_items(
		$params,'Agencies','Business::Fixflo::Agency',
	);
}

sub _get_properties {
    my ( $self,$params ) = @_;

    return $self->_get_paginator_items(
		$params,'Property/Search','Business::Fixflo::Property',
	);
}

sub _get_landlords {
    my ( $self,$params ) = @_;

    return $self->_get_paginator_items(
		$params,'Landlords','Business::Fixflo::Landlord',
	);
}

sub _get_property_addresses {
    my ( $self,$params ) = @_;

    return $self->_get_paginator_items(
		$params,'PropertyAddress/Search','Business::Fixflo::PropertyAddress',
	);
}

sub _get_issue {
    my ( $self,$id ) = @_;

    my $data = $self->_api_request( 'GET',"Issue/$id" );

    my $issue = Business::Fixflo::Issue->new(
        client => $self,
        %{ $data },
    );

    return $issue;
}

sub _get_issue_draft {
    my ( $self,$id ) = @_;

    my $data = $self->_api_request( 'GET',"IssueDraft/$id" );

    my $issue_draft = Business::Fixflo::IssueDraft->new(
        client => $self,
        %{ $data },
    );

    return $issue_draft;
}

sub _get_issue_draft_media {
    my ( $self,$id ) = @_;

    my $data = $self->_api_request( 'GET',"IssueDraftMedia/$id" );

    my $issue_draft_media = Business::Fixflo::IssueDraftMedia->new(
        client => $self,
        %{ $data },
    );

    return $issue_draft_media;
}

sub _get_agency {
    my ( $self,$id ) = @_;

    my $data = $self->_api_request( 'GET',"Agency/$id" );

    my $issue = Business::Fixflo::Agency->new(
        client => $self,
        %{ $data },
    );

    return $issue;
}

sub _get_property {
    my ( $self,$id,$is_external_id ) = @_;

    my $query = $is_external_id
        ? "ExternalPropertyRef=$id"
        : "PropertyId=$id";

    my $data = $self->_api_request( 'GET',"Property?$query" );

    my $property = Business::Fixflo::Property->new(
        client => $self,
        %{ $data },
    );

    return $property;
}

sub _get_landlord {
    my ( $self,$id,$is_email_address ) = @_;

    my $data = $is_email_address
        ? $self->_api_request( 'GET',"Landlord?EmailAddress=$id" )
        : $self->_api_request( 'GET',"Landlord?Id=$id" );

    my $landlord = Business::Fixflo::Landlord->new(
        client => $self,
        %{ $data },
    );

    return $landlord;
}

sub _get_landlord_property {
    my ( $self,$id_or_landlord_id,$property_id ) = @_;

    my $data = $property_id
        ? $self->_api_request( 'GET',"LandlordProperty?LandlordId=$id_or_landlord_id&PropertyId=$property_id" )
        : $self->_api_request( 'GET',"Landlord?LandlordPropertyId=$id_or_landlord_id" );

    my $property = Business::Fixflo::LandlordProperty->new(
        client => $self,
        %{ $data },
    );

    return $property;
}

sub _get_property_address {
    my ( $self,$id ) = @_;

    my $data = $self->_api_request( 'GET',"PropertyAddress/$id" );

    my $property_address = Business::Fixflo::PropertyAddress->new(
        client => $self,
        %{ $data },
    );

    return $property_address;
}

sub _get_quick_view_panels {
    my ( $self,$id ) = @_;

    my $data = $self->_api_request( 'GET',"qvp" );
    my @qvps;

    foreach my $qvp ( @{ $data // [] } ) {
        push( @qvps,Business::Fixflo::QuickViewPanel->new(
            client => $self,
            %{ $qvp }
        ) );
    }

    return @qvps;
}

=head1 METHODS

    api_get
    api_post
    api_delete

Make a request to the Fixflo API:

    my $data = $Client->api_get( 'Issues',\%params );

May return a L<Business::Fixflo::Paginator> object (when calling endpoints
that return lists of items) or a Business::Fixflo:: object for the Issue,
Agency, etc.

=cut

sub api_get {
    my ( $self,$path,$params ) = @_;
    return $self->_api_request( 'GET',$path,$params );
}

sub api_post {
    my ( $self,$path,$params ) = @_;
    return $self->_api_request( 'POST',$path,$params );
}

sub api_delete {
    my ( $self,$path,$params ) = @_;
    return $self->_api_request( 'DELETE',$path,$params );
}

sub _api_request {
    my ( $self,$method,$path,$params ) = @_;

    carp( "$method -> $path" )
        if $ENV{FIXFLO_DEBUG};

    my $ua = LWP::UserAgent->new;
    $ua->agent( $self->user_agent );

    $path = $self->_add_query_params( $path,$params )
        if $method eq 'GET';

    my $req = $self->_build_request( $method,$path );

    if ( $method =~ /POST|PUT|DELETE/ ) {
        if ( $params ) {
            $req->content_type( 'application/json' );
            $req->content( JSON->new->encode( $params ) );

            carp( $req->content )
                if $ENV{FIXFLO_DEBUG};
        }
    }

    if ( @{ $self->ua_proxy_settings } ) {
        $ua->proxy( $self->ua_proxy_settings );
    }

    my $res = $ua->request( $req );

    # work around the fact that a 200 status code can still mean a problem
    # with the request, which we don't discover *until* we parse envelope
    # data, at which point we have lost the request data. i can't return a
    # list from this function as that will break backwards compatibility
    # so instead i use a potentially evil global variable
    $Business::Fixflo::Client::request_data = {
        path    => $path,
        params  => $params,
        headers => $req->headers_as_string,
        content => $req->content,
    };

    if ( $res->is_success ) {
        my $data = $res->content;

        if ( $res->headers->header( 'content-type' ) =~ m!application/json! ) {
            $data = JSON->new->decode( $data );
        }

        return $data;
    }
    else {

        carp( "RES: @{[ $res->code ]}" )
            if $ENV{FIXFLO_DEBUG};

        Business::Fixflo::Exception->throw({
            message  => $res->content,
            code     => $res->code,
            response => $res->status_line,
            request  => $Business::Fixflo::Client::request_data,
        });
    }
}

sub _build_request {
    my ( $self,$method,$path ) = @_;

    my $req = HTTP::Request->new(
        # passing through the absolute URL means we don't build it
        $method => $path =~ /^http/
            ? $path : join( '/',$self->base_url . $self->api_path,$path ),
    );

    carp(
        $method => $path =~ /^http/
            ? $path : join( '/',$self->base_url . $self->api_path,$path ),
    ) if $ENV{FIXFLO_DEBUG};

    $self->_set_request_headers( $req );

    return $req;
}

sub _set_request_headers {
    my ( $self,$req ) = @_;

    my $auth_string;

    if ( $self->api_key ) {
        $auth_string = "Bearer " . $self->api_key;
    } else {
        my $username = $self->username // '';
        my $password = $self->password // '';
        $auth_string = "basic " . encode_base64( join( ":",$username,$password ) );
    }

    $req->header( 'Authorization' => $auth_string );

    carp( "Authorization: $auth_string" )
        if $ENV{FIXFLO_DEBUG};

    $req->header( 'Accept' => 'application/json' );
}

sub _add_query_params {
    my ( $self,$path,$params ) = @_;

    if ( my $query_params = $self->normalize_params( $params ) ) {
        return "$path?$query_params";
    }

    return $path;
}

=head1 AUTHOR

Lee Johnson - C<leejo@cpan.org>

This library is free software; you can redistribute it and/or modify it under
the same terms as Perl itself. If you would like to contribute documentation,
features, bug fixes, or anything else then please raise an issue / pull request:

    https://github.com/Humanstate/business-fixflo

=cut

1;

# vim: ts=4:sw=4:et


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