From: Ivan Kohler Date: Wed, 16 Jul 2014 13:17:05 +0000 (-0700) Subject: REST API, RT#28181 X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=commitdiff_plain;h=5f8111de04a4a914c72a1642722476db4728339c REST API, RT#28181 --- diff --git a/FS/FS/API.pm b/FS/FS/API.pm index 2105409c5..a0f1dba2a 100644 --- a/FS/FS/API.pm +++ b/FS/FS/API.pm @@ -476,19 +476,6 @@ Returns general customer information. Takes a hash reference as parameter with t =cut -#some false laziness w/ClientAPI::Myaccount customer_info/customer_info_short - -use vars qw( @cust_main_editable_fields @location_editable_fields ); -@cust_main_editable_fields = qw( - first last company daytime night fax mobile -); -# locale -# payby payinfo payname paystart_month paystart_year payissue payip -# ss paytype paystate stateid stateid_state -@location_editable_fields = qw( - address1 address2 city county state zip country -); - sub customer_info { my( $class, %opt ) = @_; my $conf = new FS::Conf; @@ -498,39 +485,10 @@ sub customer_info { my $cust_main = qsearchs('cust_main', { 'custnum' => $opt{custnum} }) or return { 'error' => 'Unknown custnum' }; - my %return = ( - 'error' => '', - 'display_custnum' => $cust_main->display_custnum, - 'name' => $cust_main->first. ' '. $cust_main->get('last'), - 'balance' => $cust_main->balance, - 'status' => $cust_main->status, - 'statuscolor' => $cust_main->statuscolor, - ); - - $return{$_} = $cust_main->get($_) - foreach @cust_main_editable_fields; - - for (@location_editable_fields) { - $return{$_} = $cust_main->bill_location->get($_) - if $cust_main->bill_locationnum; - $return{'ship_'.$_} = $cust_main->ship_location->get($_) - if $cust_main->ship_locationnum; - } - - my @invoicing_list = $cust_main->invoicing_list; - $return{'invoicing_list'} = - join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ); - $return{'postal_invoicing'} = - 0 < ( grep { $_ eq 'POST' } @invoicing_list ); - - #generally, the more useful data from the cust_main record the better. - # well, tell me what you want - - return \%return; + $cust_main->API_getinfo; } - =item location_info Returns location specific information for the customer. Takes a hash reference as parameter with the following keys: custnum,secret diff --git a/FS/FS/Mason.pm b/FS/FS/Mason.pm index 5476fd83e..b7aa35543 100644 --- a/FS/FS/Mason.pm +++ b/FS/FS/Mason.pm @@ -138,6 +138,7 @@ if ( -e $addl_handler_use_file ) { use FS::UI::Web qw(svc_url); use FS::UI::Web::small_custview qw(small_custview); use FS::UI::bytecount; + use FS::UI::REST qw( rest_auth rest_uri_remain encode_rest ); use FS::Msgcat qw(gettext geterror); use FS::Misc qw( send_email send_fax ocr_image states_hash counties cities state_label diff --git a/FS/FS/Record.pm b/FS/FS/Record.pm index a68442582..b226e177b 100644 --- a/FS/FS/Record.pm +++ b/FS/FS/Record.pm @@ -367,6 +367,9 @@ sub qsearch { my @bind_type = (); my $dbh = dbh; foreach my $stable ( @stable ) { + + carp '->qsearch on cust_main called' if $stable eq 'cust_main' && $DEBUG; + #stop altering the caller's hashref my $record = { %{ shift(@record) || {} } };#and be liberal in receipt my $select = shift @select; @@ -994,6 +997,8 @@ sub AUTOLOAD { eval "use FS::$table"; die $@ if $@; + carp '->cust_main called' if $table eq 'cust_main' && $DEBUG; + my $pkey_value = $self->$column(); my %search = ( $foreign_column => $pkey_value ); @@ -1122,6 +1127,13 @@ sub hashref { $self->{'Hash'}; } +#fallback +sub API_getinfo { + my $self = shift; + +{ ( map { $_=>$self->$_ } $self->fields ), + }; +} + =item modified Returns true if any of this object's values have been modified with set (or via diff --git a/FS/FS/UI/REST.pm b/FS/FS/UI/REST.pm new file mode 100644 index 000000000..b6503ba51 --- /dev/null +++ b/FS/FS/UI/REST.pm @@ -0,0 +1,38 @@ +package FS::UI::REST; +use base qw( Exporter ); + +use strict; +use vars qw( @EXPORT_OK ); +use JSON::XS; +use FS::UID qw( adminsuidsetup ); +use FS::Conf; + +@EXPORT_OK = qw( rest_auth rest_uri_remain encode_rest ); + +sub rest_auth { + my $cgi = shift; + adminsuidsetup('fs_api'); + my $conf = new FS::Conf; + die 'Incorrect shared secret' + unless $cgi->param('secret') eq $conf->config('api_shared_secret'); +} + +sub rest_uri_remain { + my($r, $m) = @_; + + #wacky way to get this... surely there must be a better way + + my $path = $m->request_comp->path; + + $r->uri =~ /\Q$path\E\/?(.*)$/ or die "$path not in ". $r->uri; + + $1; + +} + +sub encode_rest { + #XXX HTTP Accept header to send other formats besides JSON + encode_json(shift); +} + +1; diff --git a/FS/FS/cust_bill.pm b/FS/FS/cust_bill.pm index 4fb4a7d47..7cee5d78a 100644 --- a/FS/FS/cust_bill.pm +++ b/FS/FS/cust_bill.pm @@ -3243,6 +3243,14 @@ sub re_X { } +sub API_getinfo { + my $self = shift; + +{ ( map { $_=>$self->$_ } $self->fields ), + 'owed' => $self->owed, + #XXX last payment applied date + }; +} + =back =head1 CLASS METHODS diff --git a/FS/FS/cust_main.pm b/FS/FS/cust_main.pm index 9f382ac4f..f0a479997 100644 --- a/FS/FS/cust_main.pm +++ b/FS/FS/cust_main.pm @@ -8,6 +8,7 @@ use base qw( FS::cust_main::Packages FS::cust_main::Billing_ThirdParty FS::cust_main::Location FS::cust_main::Credit_Limit + FS::cust_main::API FS::otaker_Mixin FS::payinfo_Mixin FS::cust_main_Mixin FS::geocode_Mixin FS::Quotable_Mixin FS::Sales_Mixin FS::o2m_Common diff --git a/FS/FS/cust_main/API.pm b/FS/FS/cust_main/API.pm new file mode 100644 index 000000000..2637c7eba --- /dev/null +++ b/FS/FS/cust_main/API.pm @@ -0,0 +1,63 @@ +package FS::cust_main::API; + +use strict; + +#some false laziness w/ClientAPI::Myaccount customer_info/customer_info_short + +use vars qw( + @cust_main_addl_fields @cust_main_editable_fields @location_editable_fields +); +@cust_main_addl_fields = qw( + agentnum salesnum refnum classnum usernum referral_custnum +); +@cust_main_editable_fields = qw( + first last company daytime night fax mobile +); +# locale +# payby payinfo payname paystart_month paystart_year payissue payip +# ss paytype paystate stateid stateid_state +@location_editable_fields = qw( + address1 address2 city county state zip country +); + +sub API_getinfo { + my( $self, %opt ) = @_; + + my %return = ( + 'error' => '', + 'display_custnum' => $self->display_custnum, + 'name' => $self->first. ' '. $self->get('last'), + 'balance' => $self->balance, + 'status' => $self->status, + 'statuscolor' => $self->statuscolor, + ); + + $return{$_} = $self->get($_) + foreach @cust_main_editable_fields; + + unless ( $opt{'selfservice'} ) { + $return{$_} = $self->get($_) + foreach @cust_main_addl_fields; + } + + for (@location_editable_fields) { + $return{$_} = $self->bill_location->get($_) + if $self->bill_locationnum; + $return{'ship_'.$_} = $self->ship_location->get($_) + if $self->ship_locationnum; + } + + my @invoicing_list = $self->invoicing_list; + $return{'invoicing_list'} = + join(', ', grep { $_ !~ /^(POST|FAX)$/ } @invoicing_list ); + $return{'postal_invoicing'} = + 0 < ( grep { $_ eq 'POST' } @invoicing_list ); + + #generally, the more useful data from the cust_main record the better. + # well, tell me what you want + + return \%return; + +} + +1; diff --git a/FS/FS/cust_pay.pm b/FS/FS/cust_pay.pm index 775c353e4..1044e436e 100644 --- a/FS/FS/cust_pay.pm +++ b/FS/FS/cust_pay.pm @@ -892,6 +892,13 @@ sub unapplied_sql { } +sub API_getinfo { + my $self = shift; + my @fields = grep { $_ ne 'payinfo' } $self->fields; + +{ ( map { $_=>$self->$_ } @fields ), + }; +} + # _upgrade_data # # Used by FS::Upgrade to migrate to a new database. diff --git a/FS/FS/cust_pkg.pm b/FS/FS/cust_pkg.pm index af1bd835e..915f2297b 100644 --- a/FS/FS/cust_pkg.pm +++ b/FS/FS/cust_pkg.pm @@ -1,5 +1,5 @@ package FS::cust_pkg; -use base qw( FS::cust_pkg::Search +use base qw( FS::cust_pkg::Search FS::cust_pkg::API FS::otaker_Mixin FS::cust_main_Mixin FS::Sales_Mixin FS::contact_Mixin FS::location_Mixin FS::m2m_Common FS::option_Common diff --git a/FS/FS/cust_pkg/API.pm b/FS/FS/cust_pkg/API.pm new file mode 100644 index 000000000..f87eed345 --- /dev/null +++ b/FS/FS/cust_pkg/API.pm @@ -0,0 +1,13 @@ +package FS::cust_pkg::API; + +use strict; + +sub API_getinfo { + my $self = shift; + + +{ ( map { $_=>$self->$_ } $self->fields ), + }; + +} + +1; diff --git a/FS/FS/cust_svc.pm b/FS/FS/cust_svc.pm index 8fc929f29..df179f58a 100644 --- a/FS/FS/cust_svc.pm +++ b/FS/FS/cust_svc.pm @@ -904,6 +904,13 @@ sub tickets { (@tickets); } +sub API_getinfo { + my $self = shift; + my $svc_x = $self->svc_x; + +{ ( map { $_=>$self->$_ } $self->fields ), + ( map { $svc_x=>$svc_x->$_ } $svc_x->fields ), + }; +} =back diff --git a/FS/FS/part_export/internal_diddb.pm b/FS/FS/part_export/internal_diddb.pm index bb9743328..8771ae883 100644 --- a/FS/FS/part_export/internal_diddb.pm +++ b/FS/FS/part_export/internal_diddb.pm @@ -81,7 +81,7 @@ sub get_dids { }) ]; - } elsif ( $opt{'state'} ) { #return aracodes + } elsif ( $opt{'state'} ) { #return areacodes $hash{state} = $opt{state}; @@ -94,7 +94,15 @@ sub get_dids { ]; } else { - die "FS::part_export::internal_diddb::get_dids called without options\n"; + + #die "FS::part_export::internal_diddb::get_dids called without options\n"; + return [ map { $_->npa. '-'. $_->nxx. '-'. $_->station } + qsearch({ 'table' => 'phone_avail', + 'hashref' => \%hash, + 'order_by' => 'ORDER BY station', + }) + ]; + } } diff --git a/FS/FS/part_pkg.pm b/FS/FS/part_pkg.pm index 2f0646740..2ad785939 100644 --- a/FS/FS/part_pkg.pm +++ b/FS/FS/part_pkg.pm @@ -1,5 +1,7 @@ package FS::part_pkg; -use base qw( FS::m2m_Common FS::o2m_Common FS::option_Common ); +use base qw( FS::part_pkg::API + FS::m2m_Common FS::o2m_Common FS::option_Common + ); use strict; use vars qw( %plans $DEBUG $setup_hack $skip_pkg_svc_hack ); @@ -2017,8 +2019,8 @@ sub _pkgs_sql { #false laziness w/part_export & cdr my %info; foreach my $INC ( @INC ) { - warn "globbing $INC/FS/part_pkg/*.pm\n" if $DEBUG; - foreach my $file ( glob("$INC/FS/part_pkg/*.pm") ) { + warn "globbing $INC/FS/part_pkg/[a-z]*.pm\n" if $DEBUG; + foreach my $file ( glob("$INC/FS/part_pkg/[a-z]*.pm") ) { warn "attempting to load plan info from $file\n" if $DEBUG; $file =~ /\/(\w+)\.pm$/ or do { warn "unrecognized file in $INC/FS/part_pkg/: $file\n"; diff --git a/FS/FS/part_pkg/API.pm b/FS/FS/part_pkg/API.pm new file mode 100644 index 000000000..3210aa0f2 --- /dev/null +++ b/FS/FS/part_pkg/API.pm @@ -0,0 +1,17 @@ +package FS::part_pkg::API; + +use strict; + +sub API_getinfo { + my $self = shift; + #my( $self, %opt ) = @_; + + +{ ( map { $_=>$self->$_ } $self->fields ), + ( map { $_=>$self->option($_) } + qw(setup_fee recur_fee) + ), + }; + +} + +1; diff --git a/htetc/freeside-base2.4.conf b/htetc/freeside-base2.4.conf index c4e93f839..36ce3a515 100644 --- a/htetc/freeside-base2.4.conf +++ b/htetc/freeside-base2.4.conf @@ -1,5 +1,6 @@ PerlModule Apache2::compat +PerlModule DBIx::Profile #PerlModule Apache::DBI PerlModule HTML::Mason @@ -72,3 +73,9 @@ PerlSetVar FreesideHttpOnly 1 Satisfy any + + + Satisfy any + SetHandler perl-script + PerlHandler HTML::Mason + diff --git a/htetc/freeside-base2.conf b/htetc/freeside-base2.conf index 1bbe90a59..e2d507a52 100644 --- a/htetc/freeside-base2.conf +++ b/htetc/freeside-base2.conf @@ -59,3 +59,9 @@ PerlSetVar FreesideHttpOnly 1 Satisfy any + + Satisfy any + SetHandler perl-script + PerlHandler HTML::Mason + + diff --git a/httemplate/REST/1.0/.cust_bill.swp b/httemplate/REST/1.0/.cust_bill.swp new file mode 100644 index 000000000..44d4de7a9 Binary files /dev/null and b/httemplate/REST/1.0/.cust_bill.swp differ diff --git a/httemplate/REST/1.0/.cust_main.swp b/httemplate/REST/1.0/.cust_main.swp new file mode 100644 index 000000000..d785784d3 Binary files /dev/null and b/httemplate/REST/1.0/.cust_main.swp differ diff --git a/httemplate/REST/1.0/.cust_pkg.swp b/httemplate/REST/1.0/.cust_pkg.swp new file mode 100644 index 000000000..451a98554 Binary files /dev/null and b/httemplate/REST/1.0/.cust_pkg.swp differ diff --git a/httemplate/REST/1.0/.part_pkg.swp b/httemplate/REST/1.0/.part_pkg.swp new file mode 100644 index 000000000..547a79eee Binary files /dev/null and b/httemplate/REST/1.0/.part_pkg.swp differ diff --git a/httemplate/REST/1.0/.phone_avail.swp b/httemplate/REST/1.0/.phone_avail.swp new file mode 100644 index 000000000..a65bb2748 Binary files /dev/null and b/httemplate/REST/1.0/.phone_avail.swp differ diff --git a/httemplate/REST/1.0/.rate_detail.swp b/httemplate/REST/1.0/.rate_detail.swp new file mode 100644 index 000000000..8d46a280f Binary files /dev/null and b/httemplate/REST/1.0/.rate_detail.swp differ diff --git a/httemplate/REST/1.0/cust_bill b/httemplate/REST/1.0/cust_bill new file mode 100644 index 000000000..926cf3bee --- /dev/null +++ b/httemplate/REST/1.0/cust_bill @@ -0,0 +1,28 @@ +<% encode_rest($return) %>\ +<%init> + +rest_auth($cgi); + +my( $invnum, $command ) = split('/', rest_uri_remain($r, $m) ); + +my $cust_bill = qsearchs('cust_bill', { 'invnum'=>$invnum } ) + or die "unknown invnum $invnum"; + +my $return = []; + +if ( $command eq '' ) { + + my @fields = fields('cust_bill'); + $return = +{ map { $_=>$cust_bill->$_ } @fields }; + +} elsif ( $command eq 'cust_bill_pkg' ) { + + my @fields = fields('cust_bill_pkg'); + $return = [ map { my $cust_bill_pkg = $_; + +{ map { $_=>$cust_bill_pkg->$_ } @fields }; + } + $cust_bill->cust_bill_pkg + ]; +} + + diff --git a/httemplate/REST/1.0/cust_main b/httemplate/REST/1.0/cust_main new file mode 100644 index 000000000..89c558cc2 --- /dev/null +++ b/httemplate/REST/1.0/cust_main @@ -0,0 +1,81 @@ +<% encode_rest($return) %>\ +<%init> + +rest_auth($cgi); + +my( $custnum, $command ) = split('/', rest_uri_remain($r, $m), 2 ); + +if ( $r->method eq 'GET' ) { + + my $return = []; + + if ( $custnum ) { + + my $cust_main = qsearchs('cust_main', { 'custnum'=>$custnum } ) + or die "unknown custnum $custnum"; + + if ( $command eq '' ) { + + $return = $cust_main->API_getinfo; + + } elsif ( $command =~ /^(cust_(pkg|attachment|bill|pay))$/ ) { + + my $method = $1; + + $return = [ map $_->API_getinfo, $cust_main->$method ]; + + } elsif ( $command eq 'part_pkg' ) { + + my %pkgpart = map { $_->pkgpart => 1 } $cust_main->cust_pkg; + + $return = [ map $_->API_getinfo, + map qsearchs('part_pkg', { 'pkgpart'=>$_ }), + keys %pkgpart; + ]; + + } + + } else { #list + + my %hash = ( map { $_ => scalar($cgi->param($_)) } + qw( agentnum salesnum refnum classnum usernum + referral_custnum + ) + ); + + my $extra_sql = ''; + if ( $cgi->param('cust_main_invoice_dest') ) { + my $dest = dbh->quote(scalar($cgi->param('cust_main_invoice_dest'))); + $extra_sql = " + WHERE EXISTS ( SELECT 1 FROM cust_main_invoice + WHERE cust_main.custnum = cust_main_invoice.custnum + AND dest = $dest + ) + "; + } elsif ( $cgi->param('cust_main_invoice_dest_substring') ) { + my $dest = dbh->quote('%'. scalar($cgi->param('cust_main_invoice_dest_substring')). '%'); + $extra_sql = " + WHERE EXISTS ( SELECT 1 FROM cust_main_invoice + WHERE cust_main.custnum = cust_main_invoice.custnum + AND dest ILIKE $dest + ) + "; + } + + my @cust_main = qsearch({ + 'table' => 'cust_main', + 'hashref' => \%hash, + 'extra_sql' => $extra_sql; + }); + + $return = [ map $_->API_getinfo, @cust_main ]; + + } + +} elsif ( $r->method eq 'POST' ) { #create new + +} elsif ( $r->method eq 'PUT' ) { #modify + +} + + diff --git a/httemplate/REST/1.0/cust_pkg b/httemplate/REST/1.0/cust_pkg new file mode 100644 index 000000000..3c58bcf31 --- /dev/null +++ b/httemplate/REST/1.0/cust_pkg @@ -0,0 +1,39 @@ +<% encode_rest($return) %>\ +<%init> + +rest_auth($cgi); + +my( $pkgnum, $command ) = split('/', rest_uri_remain($r, $m), 2 ); + +if ( $r->method eq 'GET' ) { + + my $return = []; + + if ( $pkgnum ) { + + my $cust_pkg = qsearchs('cust_main', { 'pkgnum'=>$pkgnum } ) + or die "unknown pkgnum $pkgnum"; + + if ( $command eq '' ) { + + $return = $cust_pkg->API_getinfo; + + } elsif ( $command eq 'cust_svc' ) { + + $return = [ map $_->API_getinfo, $cust_pkg->cust_svc ]; + + } + + + + #} else { #list + + } + +} elsif ( $r->method eq 'POST' ) { #create new + +} elsif ( $r->method eq 'PUT' ) { #modify + +} + + diff --git a/httemplate/REST/1.0/part_pkg b/httemplate/REST/1.0/part_pkg new file mode 100644 index 000000000..c81b7b89b --- /dev/null +++ b/httemplate/REST/1.0/part_pkg @@ -0,0 +1,40 @@ +<% encode_rest($return) %>\ +<%init> + +rest_auth($cgi); + +my( $pkgpart, $command ) = split('/', rest_uri_remain($r, $m) ); + +my @fields = fields('part_pkg'); + +my $return = []; + +if ( $pkgpart ) { + + my $part_pkg = qsearchs('part_pkg', { 'pkgpart'=>$pkgpart } ) + or die "unknown pkgpart $pkgpart"; + + if ( $command eq '' ) { + + $return = $part_pkg->API_getinfo; + + } elsif ( $command eq 'customers' ) { + die 'XXX not yet implemented'; + #XXX redirect to a cust_main search? + } + +} else { + + my %hash = ( map { $_ => scalar($cgi->param($_)) } + qw( disabled classnum ) + ); + + my @part_pkg = qsearch('part_pkg', \%hash); + + $return = [ map $part_pkg->API_getinfo, @part_pkg ]; + +} + + + + diff --git a/httemplate/REST/1.0/phone_avail b/httemplate/REST/1.0/phone_avail new file mode 100644 index 000000000..ef9d3e7f0 --- /dev/null +++ b/httemplate/REST/1.0/phone_avail @@ -0,0 +1,25 @@ +<% encode_rest($phonenums) %>\ +<%init> + +rest_auth($cgi); + +#i'm basically a simpler misc/phonenums.cgi + +my $svcpart = $cgi->param('svcpart'); + +my $part_svc = qsearchs('part_svc', { 'svcpart'=>$svcpart } ); +die "unknown svcpart $svcpart" unless $part_svc; + +my @exports = $part_svc->part_export_did; +if ( scalar(@exports) > 1 ) { + die "more than one DID-providing export attached to svcpart $svcpart"; +} elsif ( ! @exports ) { + die "no DID providing export attached to svcpart $svcpart"; +} +my $export = $exports[0]; + +my $phonenums = $export->get_dids( map { $_ => scalar($cgi->param($_)) } + qw( ratecenter state areacode exchange ) + ); + + diff --git a/httemplate/REST/1.0/rate_detail b/httemplate/REST/1.0/rate_detail new file mode 100644 index 000000000..54e55de6c --- /dev/null +++ b/httemplate/REST/1.0/rate_detail @@ -0,0 +1,35 @@ +<% encode_rest( \@rate_detail ) %>\ +<%init> + +rest_auth($cgi); + +my $extra_sql = ''; +if ( $cgi->param('countrycode') =~ /^\+?(\d+)$/ ) { + my $countrycode = $1; + $extra_sql = " + WHERE EXISTS ( SELECT 1 rate_region + WHERE rate_detail.dest_regionnum = rate_region.regionnum + AND countrycode = '$countrycode' + "; +} + +my @detail_fields = fields('rate_detail'); +my @region_fields = fields('rate_region'); + +my @rate_detail = + map { + my $rate_detail = $_; + my $rate_region = $rate_detail->dest_region; + + +{ + ( map { $_ => $rate_detail->$_ } @detail_fields ), + ( map { $_ => $rate_region->$_ } @region_fields ), + }; + + } qsearch({ + 'table' => 'rate_detail', + 'hashref' => {}, + extra_sql => $extra_sql, + }); + +