File Coverage

lib/Business/Fixflo/Client.pm
Criterion Covered Total %
statement 159 173 91.9
branch 24 42 57.1
condition 7 13 53.8
subroutine 40 41 97.5
pod 0 4 0.0
total 230 273 84.2


line stmt bran cond sub pod time code
1             package Business::Fixflo::Client;
2              
3             =head1 NAME
4              
5             Business::Fixflo::Client
6              
7             =head1 DESCRIPTION
8              
9             This is a class for the lower level requests to the fixflo API. generally
10             there is nothing you should be doing with this.
11              
12             =cut
13              
14 16     16   1701811 use strict;
  16         178  
  16         521  
15 16     16   92 use warnings;
  16         41  
  16         477  
16              
17 16     16   8141 use Moo;
  16         173138  
  16         87  
18             with 'Business::Fixflo::Utils';
19             with 'Business::Fixflo::Version';
20              
21 16     16   29570 use Business::Fixflo::Exception;
  16         57  
  16         763  
22 16     16   8475 use Business::Fixflo::Paginator;
  16         58  
  16         710  
23 16     16   148 use Business::Fixflo::Issue;
  16         40  
  16         392  
24 16     16   9383 use Business::Fixflo::IssueDraft;
  16         56  
  16         662  
25 16     16   8290 use Business::Fixflo::IssueDraftMedia;
  16         61  
  16         823  
26 16     16   8820 use Business::Fixflo::Landlord;
  16         56  
  16         635  
27 16     16   8585 use Business::Fixflo::LandlordProperty;
  16         53  
  16         712  
28 16     16   8413 use Business::Fixflo::Agency;
  16         66  
  16         1056  
29 16     16   184 use Business::Fixflo::Property;
  16         48  
  16         411  
30 16     16   9517 use Business::Fixflo::PropertyAddress;
  16         50  
  16         512  
31 16     16   7307 use Business::Fixflo::QuickViewPanel;
  16         54  
  16         797  
32              
33 16     16   9867 use MIME::Base64 qw/ encode_base64 /;
  16         13826  
  16         1200  
34 16     16   11475 use LWP::UserAgent;
  16         716241  
  16         717  
35 16     16   193 use JSON ();
  16         40  
  16         345  
36 16     16   95 use Carp qw/ carp confess /;
  16         48  
  16         40694  
37              
38             =head1 ATTRIBUTES
39              
40             =head2 username
41              
42             Your Fixflo username (required if api_key not supplied)
43              
44             =head2 password
45              
46             Your Fixflo password (required if api_key not supplied)
47              
48             =head2 api_key
49              
50             Your Fixflo API Key (required if username and password not supplied)
51              
52             =head2 custom_domain
53              
54             Your Fixflo custom domain
55              
56             =head2 user_agent
57              
58             The user agent string used in requests to the Fixflo API, defaults to
59             business-fixflo/perl/v . $version_of_this_library.
60              
61             =head2 url_suffix
62              
63             The url suffix to use after the custom domain, defaults to fixflo.com
64              
65             =head2 base_url
66              
67             The full url to use in calling the Fixflo API, defaults to:
68              
69             value of $ENV{FIXFLO_URL}
70             or https:// $self->custom_domain . $self->url_suffix
71              
72             =head2 api_path
73              
74             The version of the Fixflo API to use, defaults to:
75              
76             /api/$Business::Fixflo::API_VERSION
77              
78             =head2 ua_proxy_settings
79              
80             The custom proxy settings for the user agent class
81             The format is identical to LWP::UserAgent::proxy
82              
83             my $ff = Business::Fixflo->new( ... );
84              
85             $ff->client->ua_proxy_settings(
86             [
87             ftp => 'http://ftp.example.com:8001/',
88             [ 'http', 'https' ] => 'http://http.example.com:8001/',
89             ]
90             );
91              
92              
93              
94             =cut
95              
96             has [ qw/ custom_domain / ] => (
97             is => 'ro',
98             required => 1,
99             );
100              
101             has [ qw/ username password api_key / ] => (
102             is => 'ro',
103             required => 0,
104             );
105              
106             has user_agent => (
107             is => 'ro',
108             default => sub {
109             require Business::Fixflo;
110             # probably want more info in here, version of perl, platform, and such
111             return "business-fixflo/perl/v" . $Business::Fixflo::VERSION;
112             }
113             );
114              
115             has url_suffix => (
116             is => 'ro',
117             required => 0,
118             default => sub { 'fixflo.com' },
119             );
120              
121             has url_scheme => (
122             is => 'ro',
123             required => 0,
124             default => sub { 'https' },
125             );
126              
127             has 'base_url' => (
128             is => 'ro',
129             required => 0,
130             lazy => 1,
131             default => sub {
132             my ( $self ) = @_;
133             return $ENV{FIXFLO_URL}
134             ? $ENV{FIXFLO_URL}
135             : $self->url_scheme . '://' . $self->custom_domain . '.' . $self->url_suffix;
136             }
137             );
138              
139             has api_path => (
140             is => 'ro',
141             required => 0,
142             default => sub { '/api/' . $Business::Fixflo::API_VERSION },
143             );
144              
145             has ua_proxy_settings => (
146             is => 'rw',
147             isa => sub {
148             confess( "$_[0] is not an ARRAY ref" )
149             if ref $_[0] ne 'ARRAY';
150             },
151             required => 0,
152             default => sub { [] },
153             );
154              
155              
156             sub BUILD {
157 20     20 0 247 my ( $self ) = @_;
158              
159 20 100 66     499 if (
160             ! $self->api_key
161             && !( $self->username && $self->password )
162             ) {
163 3         38 confess( "api_key or username + password required" );
164             }
165             }
166              
167             sub _get_issues {
168 1     1   10 my ( $self,$params ) = @_;
169              
170 1         2 return $self->_get_paginator_items(
171             $params,'Issues','Business::Fixflo::Issue',
172             );
173             }
174              
175             sub _get_paginator_items {
176 5     5   13 my ( $self,$params,$uri,$class ) = @_;
177              
178 5         15 my $items = $self->_api_request( 'GET',$uri,$params );
179              
180             my $Paginator = Business::Fixflo::Paginator->new(
181             total_items => $items->{TotalItems} // undef,
182             total_pages => $items->{TotalPages} // undef,
183             links => {
184             next => $items->{NextURL},
185             previous => $items->{PreviousURL},
186             },
187             client => $self,
188             class => $class,
189             objects => [ map { $class->new(
190             client => $self,
191             ( $uri eq 'Property/Search'
192 1         9 ? ( Address => delete( $_->{Address} ),%{ $_ } )
193             : $uri =~ /Landlords|PropertyAddress/
194 9 100       147 ? ( %{ $_ } )
  2 100       33  
195             : ( url => $_ ),
196             ),
197 5   50     68 ) } @{ $items->{Items} } ],
  5   50     15  
198             );
199              
200 5         1716 return $Paginator;
201             }
202              
203             sub _get_agencies {
204 1     1   11 my ( $self,$params ) = @_;
205              
206 1         2 return $self->_get_paginator_items(
207             $params,'Agencies','Business::Fixflo::Agency',
208             );
209             }
210              
211             sub _get_properties {
212 1     1   10 my ( $self,$params ) = @_;
213              
214 1         3 return $self->_get_paginator_items(
215             $params,'Property/Search','Business::Fixflo::Property',
216             );
217             }
218              
219             sub _get_landlords {
220 1     1   10 my ( $self,$params ) = @_;
221              
222 1         4 return $self->_get_paginator_items(
223             $params,'Landlords','Business::Fixflo::Landlord',
224             );
225             }
226              
227             sub _get_property_addresses {
228 1     1   11 my ( $self,$params ) = @_;
229              
230 1         3 return $self->_get_paginator_items(
231             $params,'PropertyAddress/Search','Business::Fixflo::PropertyAddress',
232             );
233             }
234              
235             sub _get_issue {
236 2     2   21 my ( $self,$id ) = @_;
237              
238 2         8 my $data = $self->_api_request( 'GET',"Issue/$id" );
239              
240             my $issue = Business::Fixflo::Issue->new(
241             client => $self,
242 2         9 %{ $data },
  2         41  
243             );
244              
245 2         36 return $issue;
246             }
247              
248             sub _get_issue_draft {
249 2     2   22 my ( $self,$id ) = @_;
250              
251 2         11 my $data = $self->_api_request( 'GET',"IssueDraft/$id" );
252              
253             my $issue_draft = Business::Fixflo::IssueDraft->new(
254             client => $self,
255 2         9 %{ $data },
  2         28  
256             );
257              
258 2         30 return $issue_draft;
259             }
260              
261             sub _get_issue_draft_media {
262 2     2   22 my ( $self,$id ) = @_;
263              
264 2         9 my $data = $self->_api_request( 'GET',"IssueDraftMedia/$id" );
265              
266             my $issue_draft_media = Business::Fixflo::IssueDraftMedia->new(
267             client => $self,
268 2         9 %{ $data },
  2         28  
269             );
270              
271 2         28 return $issue_draft_media;
272             }
273              
274             sub _get_agency {
275 2     2   20 my ( $self,$id ) = @_;
276              
277 2         8 my $data = $self->_api_request( 'GET',"Agency/$id" );
278              
279             my $issue = Business::Fixflo::Agency->new(
280             client => $self,
281 2         11 %{ $data },
  2         37  
282             );
283              
284 2         21 return $issue;
285             }
286              
287             sub _get_property {
288 2     2   22 my ( $self,$id,$is_external_id ) = @_;
289              
290 2 100       12 my $query = $is_external_id
291             ? "ExternalPropertyRef=$id"
292             : "PropertyId=$id";
293              
294 2         8 my $data = $self->_api_request( 'GET',"Property?$query" );
295              
296             my $property = Business::Fixflo::Property->new(
297             client => $self,
298 2         10 %{ $data },
  2         38  
299             );
300              
301 2         21 return $property;
302             }
303              
304             sub _get_landlord {
305 2     2   20 my ( $self,$id,$is_email_address ) = @_;
306              
307 2 100       12 my $data = $is_email_address
308             ? $self->_api_request( 'GET',"Landlord?EmailAddress=$id" )
309             : $self->_api_request( 'GET',"Landlord?Id=$id" );
310              
311             my $landlord = Business::Fixflo::Landlord->new(
312             client => $self,
313 2         9 %{ $data },
  2         38  
314             );
315              
316 2         20 return $landlord;
317             }
318              
319             sub _get_landlord_property {
320 0     0   0 my ( $self,$id_or_landlord_id,$property_id ) = @_;
321              
322 0 0       0 my $data = $property_id
323             ? $self->_api_request( 'GET',"LandlordProperty?LandlordId=$id_or_landlord_id&PropertyId=$property_id" )
324             : $self->_api_request( 'GET',"Landlord?LandlordPropertyId=$id_or_landlord_id" );
325              
326             my $property = Business::Fixflo::LandlordProperty->new(
327             client => $self,
328 0         0 %{ $data },
  0         0  
329             );
330              
331 0         0 return $property;
332             }
333              
334             sub _get_property_address {
335 2     2   19 my ( $self,$id ) = @_;
336              
337 2         10 my $data = $self->_api_request( 'GET',"PropertyAddress/$id" );
338              
339             my $property_address = Business::Fixflo::PropertyAddress->new(
340             client => $self,
341 2         9 %{ $data },
  2         38  
342             );
343              
344 2         21 return $property_address;
345             }
346              
347             sub _get_quick_view_panels {
348 1     1   11 my ( $self,$id ) = @_;
349              
350 1         3 my $data = $self->_api_request( 'GET',"qvp" );
351 1         6 my @qvps;
352              
353 1   50     2 foreach my $qvp ( @{ $data // [] } ) {
  1         5  
354             push( @qvps,Business::Fixflo::QuickViewPanel->new(
355             client => $self,
356 1         2 %{ $qvp }
  1         9  
357             ) );
358             }
359              
360 1         20 return @qvps;
361             }
362              
363             =head1 METHODS
364              
365             api_get
366             api_post
367             api_delete
368              
369             Make a request to the Fixflo API:
370              
371             my $data = $Client->api_get( 'Issues',\%params );
372              
373             May return a L object (when calling endpoints
374             that return lists of items) or a Business::Fixflo:: object for the Issue,
375             Agency, etc.
376              
377             =cut
378              
379             sub api_get {
380 1     1 0 149 my ( $self,$path,$params ) = @_;
381 1         3 return $self->_api_request( 'GET',$path,$params );
382             }
383              
384             sub api_post {
385 1     1 0 4 my ( $self,$path,$params ) = @_;
386 1         3 return $self->_api_request( 'POST',$path,$params );
387             }
388              
389             sub api_delete {
390 1     1 0 3 my ( $self,$path,$params ) = @_;
391 1         5 return $self->_api_request( 'DELETE',$path,$params );
392             }
393              
394             sub _api_request {
395 3     3   9 my ( $self,$method,$path,$params ) = @_;
396              
397             carp( "$method -> $path" )
398 3 50       9 if $ENV{FIXFLO_DEBUG};
399              
400 3         14 my $ua = LWP::UserAgent->new;
401 3         3596 $ua->agent( $self->user_agent );
402              
403 3 100       193 $path = $self->_add_query_params( $path,$params )
404             if $method eq 'GET';
405              
406 3         9 my $req = $self->_build_request( $method,$path );
407              
408 3 100       23 if ( $method =~ /POST|PUT|DELETE/ ) {
409 2 50       5 if ( $params ) {
410 0         0 $req->content_type( 'application/json' );
411 0         0 $req->content( JSON->new->encode( $params ) );
412              
413             carp( $req->content )
414 0 0       0 if $ENV{FIXFLO_DEBUG};
415             }
416             }
417              
418 3 50       6 if ( @{ $self->ua_proxy_settings } ) {
  3         71  
419 0         0 $ua->proxy( $self->ua_proxy_settings );
420             }
421              
422 3         33 my $res = $ua->request( $req );
423              
424             # work around the fact that a 200 status code can still mean a problem
425             # with the request, which we don't discover *until* we parse envelope
426             # data, at which point we have lost the request data. i can't return a
427             # list from this function as that will break backwards compatibility
428             # so instead i use a potentially evil global variable
429 3         17 $Business::Fixflo::Client::request_data = {
430             path => $path,
431             params => $params,
432             headers => $req->headers_as_string,
433             content => $req->content,
434             };
435              
436 3 50       324 if ( $res->is_success ) {
437 3         248 my $data = $res->content;
438              
439 3 50       177 if ( $res->headers->header( 'content-type' ) =~ m!application/json! ) {
440 3         392 $data = JSON->new->decode( $data );
441             }
442              
443 3         135 return $data;
444             }
445             else {
446              
447 0         0 carp( "RES: @{[ $res->code ]}" )
448 0 0       0 if $ENV{FIXFLO_DEBUG};
449              
450 0         0 Business::Fixflo::Exception->throw({
451             message => $res->content,
452             code => $res->code,
453             response => $res->status_line,
454             request => $Business::Fixflo::Client::request_data,
455             });
456             }
457             }
458              
459             sub _build_request {
460 3     3   6 my ( $self,$method,$path ) = @_;
461              
462 3 50       88 my $req = HTTP::Request->new(
463             # passing through the absolute URL means we don't build it
464             $method => $path =~ /^http/
465             ? $path : join( '/',$self->base_url . $self->api_path,$path ),
466             );
467              
468             carp(
469             $method => $path =~ /^http/
470             ? $path : join( '/',$self->base_url . $self->api_path,$path ),
471 3 0       10035 ) if $ENV{FIXFLO_DEBUG};
    50          
472              
473 3         11 $self->_set_request_headers( $req );
474              
475 3         143 return $req;
476             }
477              
478             sub _set_request_headers {
479 3     3   8 my ( $self,$req ) = @_;
480              
481 3         4 my $auth_string;
482              
483 3 50       11 if ( $self->api_key ) {
484 0         0 $auth_string = "Bearer " . $self->api_key;
485             } else {
486 3   50     11 my $username = $self->username // '';
487 3   50     7 my $password = $self->password // '';
488 3         67 $auth_string = "basic " . encode_base64( join( ":",$username,$password ) );
489             }
490              
491 3         24 $req->header( 'Authorization' => $auth_string );
492              
493             carp( "Authorization: $auth_string" )
494 3 50       240 if $ENV{FIXFLO_DEBUG};
495              
496 3         9 $req->header( 'Accept' => 'application/json' );
497             }
498              
499             sub _add_query_params {
500 1     1   36 my ( $self,$path,$params ) = @_;
501              
502 1 50       6 if ( my $query_params = $self->normalize_params( $params ) ) {
503 0         0 return "$path?$query_params";
504             }
505              
506 1         7 return $path;
507             }
508              
509             =head1 AUTHOR
510              
511             Lee Johnson - C
512              
513             This library is free software; you can redistribute it and/or modify it under
514             the same terms as Perl itself. If you would like to contribute documentation,
515             features, bug fixes, or anything else then please raise an issue / pull request:
516              
517             https://github.com/Humanstate/business-fixflo
518              
519             =cut
520              
521             1;
522              
523             # vim: ts=4:sw=4:et