package Mod::APIshareeio; # # SPDX-License-Identifier: AGPL-3.0-or-later # Copyright (c) Rainer Gümpelein, TeilRad GmbH # #use lib qw(/var/www/copri-bike/shareeapp-sx/src); # use warnings; use strict; use Exporter; our @ISA = qw (Exporter); use POSIX; use CGI; use Apache2::Const -compile => qw(OK ); use JSON; use Scalar::Util qw(looks_like_number); use Config::General; use Lib::Config; use Mod::DBtank; use Mod::Basework; use Mod::Shareework; use Mod::APIfunc; use Data::Dumper; sub handler { my ($r) = @_; my $q = new CGI; my $netloc = $q->url(-base=>1); $q->import_names('R'); my $json = JSON->new->allow_nonref; my $cf = new Config; my $dbt = new DBtank; my $bw = new Basework; my $tk = new Shareework; my %varenv = $cf->envonline(); my $oprefix = $dbt->{operator}->{$varenv{dbname}}->{oprefix}; my $now_dt = strftime "%Y-%m-%d %H:%M:%S", localtime; my @keywords = $q->param; my $user_agent = $q->user_agent(); my $aowner = 168; my $debug=1; my $api_file = "/var/www/copri4/shareeconf/apikeys.cfg"; my $aconf = Config::General->new($api_file); my %apikeyconf = $aconf->getall; my %headers = map { $_ => $q->http($_) } $q->http(); $bw->log("APIshareeio request:\n--> user-agent '$user_agent' ",$q,""); $bw->log("headers:",\%headers,""); #for my $header ( keys %headers ) { # print "$header: $headers{$header}\n"; #} print $q->header(-type => "application/json", -charset => "utf-8", -'Access-Control-Allow-Origin' => "*"); open(FILE,">>$varenv{logdir}/APIshareeio.log") if($debug); print FILE "\n*** $now_dt user-agent: '$user_agent' to syshost: $varenv{syshost}\n" if($debug); print FILE "<=== DUMP query\n " . Dumper($q) . "\n" if($debug); print FILE "<=== DUMP postdata:\n " . Dumper($q->param('POSTDATA')) . "\n" if($debug); my $response = { event => "", response_state => "Event doesn't match", }; my $http_sharee_api_key = $headers{HTTP_SHAREE_API_KEY} || $R::HTTP_SHAREE_API_KEY || ""; if(!$apikeyconf{shareeio}->{sharee_api_key} || !$http_sharee_api_key || $apikeyconf{shareeio}->{sharee_api_key} ne $http_sharee_api_key){ $response->{response_state} = "Failure: access denied, api-key doesn't match"; $bw->log("Failure: access denied, api-key doesn't match",$q,""); my $jrout = $json->pretty->encode({shareeio => $response}); print $jrout; return Apache2::Const::OK; exit 0; } foreach(@keywords){ if(length($_) > 40 || length($q->param($_)) > 400){ $response->{response_state} = "Failure 9000: amount of characters in $_ exceeds"; $bw->log("Failure 9000: amount of characters in $_ exceeds",$q,""); my $jrout = $json->pretty->encode({shareeio => $response}); print $jrout; return Apache2::Const::OK; exit 0; } } #sig booking_update my $response_out = {}; $response_out = sig_booking_update($q,\%varenv,$response,$aowner);# sig json post #sig json api sub sig_booking_update { my $q = shift; my $varenv = shift; my $response = shift || {}; my $aowner = shift || ""; $q->import_names('R'); my $dbt = new DBtank; my $apif = new APIfunc; my $dbh = ""; my $debug=1; my $jrout = "seems to be not valid"; #sig using POST JSON my $POSTDATA = $q->param('POSTDATA'); #REST methode only used for easy testing if($R::request && $R::request eq "booking_update"){ my $rentalId = $q->escapeHTML($R::rentalId) || ""; my %POSTDATA_hash = ( "event"=>"RENTAL_END", "data"=>{ "bikeId"=>"test16b5-0522-43da-ab66-477744a731a3", "lockStatus"=>"locked", "rentalId"=>"$rentalId", "reservationState"=>"", "startTime"=>"2022-06-06T15:00:18.045Z", "endTime"=>"2022-06-06T15:01:43.118Z", "distance"=>1000 } ); $POSTDATA = encode_json(\%POSTDATA_hash); print FILE "<=== DUMP POSTDATA_hash:\n " . Dumper(\%POSTDATA_hash) . "\n" if($debug); } eval { my $response_in = {}; $response_in = decode_json($POSTDATA) if($POSTDATA); $response->{event} = "$response_in->{event}"; if($response_in->{event}){ my $bikeId = $q->escapeHTML($response_in->{data}->{bikeId}) || "";#on push, bikeId is bike_id my $rentalId = $q->escapeHTML($response_in->{data}->{rentalId}) || ""; my $rows = 0; my $booking_values = {}; print FILE "event: $response_in->{event}\n" if($debug); if($response_in->{event} eq "BIKE_STATUS" || $response_in->{event} eq "BATTERY_CAPACITY"){ $response->{response_state} = "OK: $response_in->{event} methode not implemented, because we will get it just-in-time on requesting sig GET bikes"; } if($response_in->{event} eq "RENTAL_START"){ $response->{response_state} = "OK: methode not implemented, because rental will be started by App and success returned by sig-connector"; } elsif($response_in->{event} eq "RENTAL_END" || $response_in->{event} eq "RESERVATION_END" || ($response_in->{event} eq "SMARTLOCK" && $response_in->{data}->{lockStatus} eq "unlocked")){ if($rentalId){ my $ctpos = {}; my $booking_pos = { table => "contenttranspos", fetch => "one", txt11 => "$rentalId", int10 => "IN::('2','3')", #ca_id => "$authraw->{c_id}",#sig doesn't know uid }; #only reserved alias requested rentals can be canceled $booking_pos->{int10} = 2 if($response_in->{event} eq "RESERVATION_END"); $ctpos = $dbt->fetch_tablerecord($dbh,$booking_pos); my $distance = 0; $distance = $response_in->{data}->{distance} / 1000 if(looks_like_number($response_in->{data}->{distance}) && $response_in->{data}->{distance} > 0); $distance = sprintf('%.2f',$distance); if(ref($ctpos) eq "HASH" && $ctpos->{txt22} && $ctpos->{txt11}){ my $sig_book = { bikeId => $ctpos->{txt22}, rentalId => $ctpos->{txt11}, reservationId => "", distance => $distance, }; my $authraw = { c_id => "" }; $authraw->{c_id} = $ctpos->{ca_id} if($ctpos->{ca_id}); $q->param(-name=>'bike',-value=>"$ctpos->{ct_name}") if($ctpos->{ct_name}); $q->param(-name=>'state',-value=>"canceled") if($response_in->{event} eq "RESERVATION_END"); if($response_in->{event} eq "SMARTLOCK" && $response_in->{data}->{lockStatus} eq "unlocked"){ $q->param(-name=>'lock_state',-value=>"unlocked"); } if($response_in->{event} eq "RENTAL_END"){ $q->param(-name=>'state',-value=>"available"); $q->param(-name=>'lock_state',-value=>"locked"); #addition state in context to get state=returned for miniquery $sig_book->{station_lock_state} = "3";#set int28 to 3 } ($rows, $booking_values) = $apif->booking_update($q,$varenv,$authraw,$aowner,$sig_book) if($authraw->{c_id}); #response is for sig json after rental-end $booking_values->{bikeId} = $sig_book->{bikeId}; $booking_values->{rentalId} = $sig_book->{rentalId}; delete $booking_values->{geo_distance}; delete $booking_values->{co2saving}; delete $booking_values->{bike}; delete $booking_values->{response_text}; delete $booking_values->{user_miniquery}; $response->{response_state} = "OK: event matched, but something seems to goes wrong on booking_update" if(!$booking_values->{response_state}); $response = {%$response, %$booking_values}; }else{ $response->{response_state} = "Failure: $response_in->{event} : there is no reservation OR rental with rentalID=$rentalId"; } }else{ $response->{response_state} = "Failure: no rentalId defined"; } }#end RENTAL_END }#end event };#end eval if ($@){ print FILE "failure! can not decode POST json, POSTDATA:\n" . Dumper($q->param('POSTDATA')) . "\n" if($debug); #warn $@; print FILE "warn:" . $@ . "\n" if($debug); $response->{response_state} = "Failure: can not decode POST json"; } return $response; }#end sig json booking_update #end RESTful ------------------------------------------------------------ #FINAL JSON response OUTPUT ---------------------------------------------------------- my $jrout = $json->pretty->encode({shareeio => $response_out}); print $jrout; print FILE "APIshareeio jrout:\n" . Dumper($jrout) . "\n" if($debug); $bw->log("APIshareeio response by $user_agent mapped aowner:$aowner",$jrout,""); #end JSON ---------------------------------------------------------------------------- close(FILE) if($debug); return Apache2::Const::OK; }#end handler 1;