From 65b2893f9c2512e51d66885a393b77f84efa8330 Mon Sep 17 00:00:00 2001 From: M Somerville Date: Fri, 23 Oct 2020 07:26:00 +0100 Subject: [Bromley] Waste notification endpoint. --- perllib/FixMyStreet/App/Controller/Waste.pm | 92 +++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 perllib/FixMyStreet/App/Controller/Waste.pm (limited to 'perllib/FixMyStreet/App/Controller/Waste.pm') diff --git a/perllib/FixMyStreet/App/Controller/Waste.pm b/perllib/FixMyStreet/App/Controller/Waste.pm new file mode 100644 index 000000000..3cdd9fd42 --- /dev/null +++ b/perllib/FixMyStreet/App/Controller/Waste.pm @@ -0,0 +1,92 @@ +package FixMyStreet::App::Controller::Waste; +use Moose; +use namespace::autoclean; + +BEGIN { extends 'Catalyst::Controller' } + +use utf8; +use Open311::GetServiceRequestUpdates; + +sub receive_echo_event_notification : Path('/waste/echo') : Args(0) { + my ($self, $c) = @_; + $c->stash->{format} = 'xml'; + $c->response->header(Content_Type => 'application/soap+xml'); + + require SOAP::Lite; + + $c->detach('soap_error', [ 'Invalid method', 405 ]) unless $c->req->method eq 'POST'; + + my $echo = $c->cobrand->feature('echo'); + $c->detach('soap_error', [ 'Missing config', 500 ]) unless $echo; + + # Make sure we log entire request for debugging + $c->detach('soap_error', [ 'Missing body' ]) unless $c->req->body; + my $soap = join('', $c->req->body->getlines); + $c->log->info($soap); + + my $body = $c->cobrand->body; + $c->detach('soap_error', [ 'Bad jurisdiction' ]) unless $body; + + my $env = SOAP::Deserializer->deserialize($soap); + + my $header = $env->header; + $c->detach('soap_error', [ 'Missing SOAP header' ]) unless $header; + my $action = $header->{Action}; + $c->detach('soap_error', [ 'Incorrect Action' ]) unless $action && $action eq $echo->{receive_action}; + $header = $header->{Security}; + $c->detach('soap_error', [ 'Missing Security header' ]) unless $header; + my $token = $header->{UsernameToken}; + $c->detach('soap_error', [ 'Authentication failed' ]) + unless $token && $token->{Username} eq $echo->{receive_username} && $token->{Password} eq $echo->{receive_password}; + + # Still want to say it is okay, even if we did nothing with it + $c->forward('soap_ok'); +} + +sub soap_error : Private { + my ($self, $c, $comment, $code) = @_; + $code ||= 400; + $c->response->status($code); + my $type = $code == 500 ? 'Server' : 'Client'; + $c->response->body(SOAP::Serializer->fault($type, "Bad request: $comment", soap_header())); +} + +sub soap_ok : Private { + my ($self, $c) = @_; + $c->response->status(200); + my $method = SOAP::Data->name("NotifyEventUpdatedResponse")->attr({ + xmlns => "http://www.twistedfish.com/xmlns/echo/api/v1" + }); + $c->response->body(SOAP::Serializer->envelope(method => $method, soap_header())); +} + +sub soap_header { + my $attr = "http://www.twistedfish.com/xmlns/echo/api/v1"; + my $action = "NotifyEventUpdatedResponse"; + my $header = SOAP::Header->name("Action")->attr({ + xmlns => 'http://www.w3.org/2005/08/addressing', + 'soap:mustUnderstand' => 1, + })->value("$attr/ReceiverService/$action"); + + my $dt = DateTime->now(); + my $dt2 = $dt->clone->add(minutes => 5); + my $w3c = DateTime::Format::W3CDTF->new; + my $header2 = SOAP::Header->name("Security")->attr({ + 'soap:mustUnderstand' => 'true', + 'xmlns' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' + })->value( + \SOAP::Header->name( + "Timestamp" => \SOAP::Header->value( + SOAP::Header->name('Created', $w3c->format_datetime($dt)), + SOAP::Header->name('Expires', $w3c->format_datetime($dt2)), + ) + )->attr({ + xmlns => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", + }) + ); + return ($header, $header2); +} + +__PACKAGE__->meta->make_immutable; + +1; -- cgit v1.2.3 From ce1b3ec61fdaa954c26e55b8ce8cd1ad619b3538 Mon Sep 17 00:00:00 2001 From: Matthew Somerville Date: Mon, 29 Jun 2020 15:00:55 +0100 Subject: [Bromley] Add waste service lookup. This creates an integration to view bin collection days, and placeholders for the start of a non-map property-based reporting flow. --- perllib/FixMyStreet/App/Controller/Waste.pm | 131 ++++++++++++++-------------- 1 file changed, 64 insertions(+), 67 deletions(-) (limited to 'perllib/FixMyStreet/App/Controller/Waste.pm') diff --git a/perllib/FixMyStreet/App/Controller/Waste.pm b/perllib/FixMyStreet/App/Controller/Waste.pm index 3cdd9fd42..6319b5f22 100644 --- a/perllib/FixMyStreet/App/Controller/Waste.pm +++ b/perllib/FixMyStreet/App/Controller/Waste.pm @@ -5,86 +5,83 @@ use namespace::autoclean; BEGIN { extends 'Catalyst::Controller' } use utf8; -use Open311::GetServiceRequestUpdates; +use FixMyStreet::App::Form::Waste::UPRN; -sub receive_echo_event_notification : Path('/waste/echo') : Args(0) { - my ($self, $c) = @_; - $c->stash->{format} = 'xml'; - $c->response->header(Content_Type => 'application/soap+xml'); +sub auto : Private { + my ( $self, $c ) = @_; + my $cobrand_check = $c->cobrand->feature('waste'); + $c->detach( '/page_error_404_not_found' ) if !$cobrand_check; + return 1; +} - require SOAP::Lite; +sub index : Path : Args(0) { + my ( $self, $c ) = @_; + + if (my $uprn = $c->get_param('address')) { + $c->detach('redirect_to_uprn', [ $uprn ]); + } + + $c->stash->{title} = 'What is your address?'; + my $form = FixMyStreet::App::Form::Waste::UPRN->new( cobrand => $c->cobrand ); + $form->process( params => $c->req->body_params ); + if ($form->validated) { + my $addresses = $form->value->{postcode}; + $form = address_list_form($addresses); + } + $c->stash->{form} = $form; +} - $c->detach('soap_error', [ 'Invalid method', 405 ]) unless $c->req->method eq 'POST'; +sub address_list_form { + my $addresses = shift; + HTML::FormHandler->new( + field_list => [ + address => { + required => 1, + type => 'Select', + widget => 'RadioGroup', + label => 'Select an address', + tags => { last_differs => 1, small => 1 }, + options => $addresses, + }, + go => { + type => 'Submit', + value => 'Continue', + element_attr => { class => 'govuk-button' }, + }, + ], + ); +} - my $echo = $c->cobrand->feature('echo'); - $c->detach('soap_error', [ 'Missing config', 500 ]) unless $echo; +sub redirect_to_uprn : Private { + my ($self, $c, $uprn) = @_; + my $uri = '/waste/uprn/' . $uprn; + $c->res->redirect($uri); + $c->detach; +} - # Make sure we log entire request for debugging - $c->detach('soap_error', [ 'Missing body' ]) unless $c->req->body; - my $soap = join('', $c->req->body->getlines); - $c->log->info($soap); +sub uprn : Chained('/') : PathPart('waste/uprn') : CaptureArgs(1) { + my ($self, $c, $uprn) = @_; - my $body = $c->cobrand->body; - $c->detach('soap_error', [ 'Bad jurisdiction' ]) unless $body; + if ($uprn eq 'missing') { + $c->stash->{template} = 'waste/missing.html'; + $c->detach; + } - my $env = SOAP::Deserializer->deserialize($soap); + $c->forward('/auth/get_csrf_token'); - my $header = $env->header; - $c->detach('soap_error', [ 'Missing SOAP header' ]) unless $header; - my $action = $header->{Action}; - $c->detach('soap_error', [ 'Incorrect Action' ]) unless $action && $action eq $echo->{receive_action}; - $header = $header->{Security}; - $c->detach('soap_error', [ 'Missing Security header' ]) unless $header; - my $token = $header->{UsernameToken}; - $c->detach('soap_error', [ 'Authentication failed' ]) - unless $token && $token->{Username} eq $echo->{receive_username} && $token->{Password} eq $echo->{receive_password}; + my $property = $c->stash->{property} = $c->cobrand->call_hook(look_up_property => $uprn); + $c->detach( '/page_error_404_not_found', [] ) unless $property; - # Still want to say it is okay, even if we did nothing with it - $c->forward('soap_ok'); -} + $c->stash->{uprn} = $uprn; + $c->stash->{latitude} = $property->{latitude}; + $c->stash->{longitude} = $property->{longitude}; -sub soap_error : Private { - my ($self, $c, $comment, $code) = @_; - $code ||= 400; - $c->response->status($code); - my $type = $code == 500 ? 'Server' : 'Client'; - $c->response->body(SOAP::Serializer->fault($type, "Bad request: $comment", soap_header())); + $c->stash->{service_data} = $c->cobrand->call_hook(bin_services_for_address => $property) || []; + $c->stash->{services} = { map { $_->{service_id} => $_ } @{$c->stash->{service_data}} }; } -sub soap_ok : Private { +sub bin_days : Chained('uprn') : PathPart('') : Args(0) { my ($self, $c) = @_; - $c->response->status(200); - my $method = SOAP::Data->name("NotifyEventUpdatedResponse")->attr({ - xmlns => "http://www.twistedfish.com/xmlns/echo/api/v1" - }); - $c->response->body(SOAP::Serializer->envelope(method => $method, soap_header())); -} - -sub soap_header { - my $attr = "http://www.twistedfish.com/xmlns/echo/api/v1"; - my $action = "NotifyEventUpdatedResponse"; - my $header = SOAP::Header->name("Action")->attr({ - xmlns => 'http://www.w3.org/2005/08/addressing', - 'soap:mustUnderstand' => 1, - })->value("$attr/ReceiverService/$action"); - - my $dt = DateTime->now(); - my $dt2 = $dt->clone->add(minutes => 5); - my $w3c = DateTime::Format::W3CDTF->new; - my $header2 = SOAP::Header->name("Security")->attr({ - 'soap:mustUnderstand' => 'true', - 'xmlns' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' - })->value( - \SOAP::Header->name( - "Timestamp" => \SOAP::Header->value( - SOAP::Header->name('Created', $w3c->format_datetime($dt)), - SOAP::Header->name('Expires', $w3c->format_datetime($dt2)), - ) - )->attr({ - xmlns => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", - }) - ); - return ($header, $header2); } __PACKAGE__->meta->make_immutable; -- cgit v1.2.3 From dbde76a5090e772c806bd189426ee078b9b7e4ba Mon Sep 17 00:00:00 2001 From: Matthew Somerville Date: Mon, 29 Jun 2020 16:07:52 +0100 Subject: [Bromley] Add waste reporting service. This creates a non-map property-based reporting flow for reporting missed collections and requesting new containers. Such reports are always private, in categories that are hidden from the map filters and never shown on .com. --- perllib/FixMyStreet/App/Controller/Waste.pm | 263 ++++++++++++++++++++++++++++ 1 file changed, 263 insertions(+) (limited to 'perllib/FixMyStreet/App/Controller/Waste.pm') diff --git a/perllib/FixMyStreet/App/Controller/Waste.pm b/perllib/FixMyStreet/App/Controller/Waste.pm index 6319b5f22..667edad10 100644 --- a/perllib/FixMyStreet/App/Controller/Waste.pm +++ b/perllib/FixMyStreet/App/Controller/Waste.pm @@ -5,7 +5,11 @@ use namespace::autoclean; BEGIN { extends 'Catalyst::Controller' } use utf8; +use Lingua::EN::Inflect qw( NUMWORDS ); use FixMyStreet::App::Form::Waste::UPRN; +use FixMyStreet::App::Form::Waste::AboutYou; +use FixMyStreet::App::Form::Waste::Request; +use FixMyStreet::App::Form::Waste::Report; sub auto : Private { my ( $self, $c ) = @_; @@ -55,6 +59,9 @@ sub address_list_form { sub redirect_to_uprn : Private { my ($self, $c, $uprn) = @_; my $uri = '/waste/uprn/' . $uprn; + my $type = $c->get_param('type') || ''; + $uri .= '/request' if $type eq 'request'; + $uri .= '/report' if $type eq 'report'; $c->res->redirect($uri); $c->detach; } @@ -84,6 +91,262 @@ sub bin_days : Chained('uprn') : PathPart('') : Args(0) { my ($self, $c) = @_; } +sub construct_bin_request_form { + my $c = shift; + + my $field_list = []; + + foreach (@{$c->stash->{service_data}}) { + next unless $_->{next}; + my $name = $_->{service_name}; + my $containers = $_->{request_containers}; + my $max = $_->{request_max}; + foreach my $id (@$containers) { + push @$field_list, "container-$id" => { + type => 'Checkbox', + apply => [ + { + when => { "quantity-$id" => sub { $_[0] > 0 } }, + check => qr/^1$/, + message => 'Please tick the box', + }, + ], + label => $name, + option_label => $c->stash->{containers}->{$id}, + tags => { toggle => "form-quantity-$id-row" }, + }; + $name = ''; # Only on first container + push @$field_list, "quantity-$id" => { + type => 'Select', + label => 'Quantity', + tags => { + hint => "You can request a maximum of " . NUMWORDS($max) . " containers", + initial_hidden => 1, + }, + options => [ + { value => "", label => '-' }, + map { { value => $_, label => $_ } } (1..$max), + ], + required_when => { "container-$id" => 1 }, + }; + } + } + + return $field_list; +} + +sub request : Chained('uprn') : Args(0) { + my ($self, $c) = @_; + + my $field_list = construct_bin_request_form($c); + + $c->stash->{first_page} = 'request'; + $c->stash->{form_class} = 'FixMyStreet::App::Form::Waste::Request'; + $c->stash->{page_list} = [ + request => { + fields => [ grep { ! ref $_ } @$field_list, 'submit' ], + title => 'Which containers do you need?', + next => 'about_you', + }, + ]; + $c->stash->{field_list} = $field_list; + $c->forward('form'); +} + +sub process_request_data : Private { + my ($self, $c, $form) = @_; + my $data = $form->saved_data; + my $address = $c->stash->{property}->{address}; + my @services = grep { /^container-/ && $data->{$_} } keys %$data; + foreach (@services) { + my ($id) = /container-(.*)/; + my $container = $c->stash->{containers}{$id}; + my $quantity = $data->{"quantity-$id"}; + $data->{title} = "Request new $container"; + $data->{detail} = "Quantity: $quantity\n\n$address"; + $c->set_param('Container_Type', $id); + $c->set_param('Quantity', $quantity); + $c->forward('add_report', [ $data ]) or return; + push @{$c->stash->{report_ids}}, $c->stash->{report}->id; + } + return 1; +} + +sub construct_bin_report_form { + my $c = shift; + + my $field_list = []; + + foreach (@{$c->stash->{service_data}}) { + next unless $_->{last}; + my $id = $_->{service_id}; + my $name = $_->{service_name}; + push @$field_list, "service-$id" => { + type => 'Checkbox', + label => $name, + option_label => $name, + }; + } + + return $field_list; +} + +sub report : Chained('uprn') : Args(0) { + my ($self, $c) = @_; + + my $field_list = construct_bin_report_form($c); + + $c->stash->{first_page} = 'report'; + $c->stash->{form_class} = 'FixMyStreet::App::Form::Waste::Report'; + $c->stash->{page_list} = [ + report => { + fields => [ grep { ! ref $_ } @$field_list, 'submit' ], + title => 'Select your missed collection', + next => 'about_you', + }, + ]; + $c->stash->{field_list} = $field_list; + $c->forward('form'); +} + +sub process_report_data : Private { + my ($self, $c, $form) = @_; + my $data = $form->saved_data; + my $address = $c->stash->{property}->{address}; + my @services = grep { /^service-/ && $data->{$_} } keys %$data; + foreach (@services) { + my ($id) = /service-(.*)/; + my $service = $c->stash->{services}{$id}{service_name}; + $data->{title} = "Report missed $service"; + $data->{detail} = "$data->{title}\n\n$address"; + $c->set_param('service_id', $id); + $c->forward('add_report', [ $data ]) or return; + push @{$c->stash->{report_ids}}, $c->stash->{report}->id; + } + return 1; +} + +sub load_form { + my ($c, $previous_form) = @_; + + my $page; + if ($previous_form) { + $page = $previous_form->next; + } else { + $page = $c->forward('get_page'); + } + + my $form = $c->stash->{form_class}->new( + page_list => $c->stash->{page_list}, + $c->stash->{field_list} ? (field_list => $c->stash->{field_list}) : (), + page_name => $page, + csrf_token => $c->stash->{csrf_token}, + c => $c, + previous_form => $previous_form, + saved_data_encoded => $c->get_param('saved_data'), + no_preload => 1, + ); + + if (!$form->has_current_page) { + $c->detach('/page_error_400_bad_request', [ 'Bad request' ]); + } + + return $form; +} + +sub form : Private { + my ($self, $c) = @_; + + my $form = load_form($c); + if ($c->get_param('process')) { + $c->forward('/auth/check_csrf_token'); + $form->process(params => $c->req->body_params); + if ($form->validated) { + $form = load_form($c, $form); + } + } + + $form->process unless $form->processed; + + $c->stash->{template} = $form->template || 'waste/index.html'; + $c->stash->{form} = $form; +} + +sub get_page : Private { + my ($self, $c) = @_; + + my $goto = $c->get_param('goto') || ''; + my $process = $c->get_param('process') || ''; + $goto = $c->stash->{first_page} unless $goto || $process; + if ($goto && $process) { + $c->detach('/page_error_400_bad_request', [ 'Bad request' ]); + } + + return $goto || $process; +} + +sub add_report : Private { + my ( $self, $c, $data ) = @_; + + $c->stash->{cobrand_data} = 'waste'; + + # XXX Is this best way to do this? + if ($c->user_exists && $c->user->from_body && $c->user->email ne $data->{email}) { + $c->set_param('form_as', 'another_user'); + $c->set_param('username', $data->{email} || $data->{phone}); + } else { + $c->set_param('username_register', $data->{email} || $data->{phone}); + } + + # Set the data as if a new report form has been submitted + + $c->set_param('submit_problem', 1); + $c->set_param('pc', ''); + $c->set_param('non_public', 1); + + $c->set_param('name', $data->{name}); + $c->set_param('phone', $data->{phone}); + + $c->set_param('category', $data->{category}); + $c->set_param('title', $data->{title}); + $c->set_param('detail', $data->{detail}); + $c->set_param('uprn', $c->stash->{uprn}); + + $c->forward('setup_categories_and_bodies') unless $c->stash->{contacts}; + $c->forward('/report/new/non_map_creation', [['/waste/remove_name_errors']]) or return; + my $report = $c->stash->{report}; + $report->confirm; + $report->update; + + $c->model('DB::Alert')->find_or_create({ + user => $report->user, + alert_type => 'new_updates', + parameter => $report->id, + cobrand => $report->cobrand, + lang => $report->lang, + })->confirm; + + return 1; +} + +sub remove_name_errors : Private { + my ($self, $c) = @_; + # We do not mind about missing title/split name here + my $field_errors = $c->stash->{field_errors}; + delete $field_errors->{fms_extra_title}; + delete $field_errors->{first_name}; + delete $field_errors->{last_name}; +} + +sub setup_categories_and_bodies : Private { + my ($self, $c) = @_; + + $c->stash->{all_areas} = $c->stash->{all_areas_mapit} = { $c->cobrand->council_area_id => { id => $c->cobrand->council_area_id } }; + $c->forward('/report/new/setup_categories_and_bodies'); + my $contacts = $c->stash->{contacts}; + @$contacts = grep { grep { $_ eq 'Waste' } @{$_->groups} } @$contacts; +} + __PACKAGE__->meta->make_immutable; 1; -- cgit v1.2.3 From 613f45c6514ace275b3a49a5d50f7b83ed536b5f Mon Sep 17 00:00:00 2001 From: Matthew Somerville Date: Mon, 29 Jun 2020 16:08:54 +0100 Subject: [Bromley] Add general enquiries. This adds general enquiries to the reporting flow. --- perllib/FixMyStreet/App/Controller/Waste.pm | 72 +++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) (limited to 'perllib/FixMyStreet/App/Controller/Waste.pm') diff --git a/perllib/FixMyStreet/App/Controller/Waste.pm b/perllib/FixMyStreet/App/Controller/Waste.pm index 667edad10..a31c359f1 100644 --- a/perllib/FixMyStreet/App/Controller/Waste.pm +++ b/perllib/FixMyStreet/App/Controller/Waste.pm @@ -10,6 +10,7 @@ use FixMyStreet::App::Form::Waste::UPRN; use FixMyStreet::App::Form::Waste::AboutYou; use FixMyStreet::App::Form::Waste::Request; use FixMyStreet::App::Form::Waste::Report; +use FixMyStreet::App::Form::Waste::Enquiry; sub auto : Private { my ( $self, $c ) = @_; @@ -226,6 +227,77 @@ sub process_report_data : Private { return 1; } +sub enquiry : Chained('uprn') : Args(0) { + my ($self, $c) = @_; + + if (my $template = $c->get_param('template')) { + $c->stash->{template} = "waste/enquiry-$template.html"; + $c->detach; + } + + $c->forward('setup_categories_and_bodies'); + + my $category = $c->get_param('category'); + my $service = $c->get_param('service_id'); + if (!$category || !$service || !$c->stash->{services}{$service}) { + $c->res->redirect('/waste/uprn/' . $c->stash->{uprn}); + $c->detach; + } + my ($contact) = grep { $_->category eq $category } @{$c->stash->{contacts}}; + if (!$contact) { + $c->res->redirect('/waste/uprn/' . $c->stash->{uprn}); + $c->detach; + } + + my $field_list = []; + foreach (@{$contact->get_metadata_for_input}) { + next if $_->{code} eq 'service_id' || $_->{code} eq 'uprn'; + my $type = 'Text'; + $type = 'TextArea' if 'text' eq ($_->{datatype} || ''); + my $required = $_->{required} eq 'true' ? 1 : 0; + push @$field_list, "extra_$_->{code}" => { + type => $type, label => $_->{description}, required => $required + }; + } + + $c->stash->{first_page} = 'enquiry'; + $c->stash->{form_class} = 'FixMyStreet::App::Form::Waste::Enquiry'; + $c->stash->{page_list} = [ + enquiry => { + fields => [ 'category', 'service_id', grep { ! ref $_ } @$field_list, 'continue' ], + title => $category, + next => 'about_you', + update_field_list => sub { + my $form = shift; + my $c = $form->c; + return { + category => { default => $c->get_param('category') }, + service_id => { default => $c->get_param('service_id') }, + } + } + }, + ]; + $c->stash->{field_list} = $field_list; + $c->forward('form'); +} + +sub process_enquiry_data : Private { + my ($self, $c, $form) = @_; + my $data = $form->saved_data; + my $address = $c->stash->{property}->{address}; + $data->{title} = $data->{category}; + $data->{detail} = "$data->{category}\n\n$address"; + # Read extra details in loop + foreach (grep { /^extra_/ } keys %$data) { + my ($id) = /^extra_(.*)/; + $c->set_param($id, $data->{$_}); + } + $c->set_param('service_id', $data->{service_id}); + $c->forward('add_report', [ $data ]) or return; + push @{$c->stash->{report_ids}}, $c->stash->{report}->id; + return 1; +} + sub load_form { my ($c, $previous_form) = @_; -- cgit v1.2.3 From c0f9b0dcb1fc7cecb4b98ad82715600f73707bf0 Mon Sep 17 00:00:00 2001 From: Matthew Somerville Date: Tue, 16 Jun 2020 16:25:07 +0100 Subject: [Waste] ICal generation, with Data::ICal. --- perllib/FixMyStreet/App/Controller/Waste.pm | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'perllib/FixMyStreet/App/Controller/Waste.pm') diff --git a/perllib/FixMyStreet/App/Controller/Waste.pm b/perllib/FixMyStreet/App/Controller/Waste.pm index a31c359f1..e606b1d8c 100644 --- a/perllib/FixMyStreet/App/Controller/Waste.pm +++ b/perllib/FixMyStreet/App/Controller/Waste.pm @@ -92,6 +92,44 @@ sub bin_days : Chained('uprn') : PathPart('') : Args(0) { my ($self, $c) = @_; } +sub calendar : Chained('uprn') : PathPart('calendar.ics') : Args(0) { + my ($self, $c) = @_; + $c->res->header(Content_Type => 'text/calendar'); + require Data::ICal::RFC7986; + require Data::ICal::Entry::Event; + my $calendar = Data::ICal::RFC7986->new( + calname => 'Bin calendar', + rfc_strict => 1, + auto_uid => 1, + ); + $calendar->add_properties( + prodid => '//FixMyStreet//Bin Collection Calendars//EN', + method => 'PUBLISH', + 'refresh-interval' => [ 'P1D', { value => 'DURATION' } ], + 'x-published-ttl' => 'P1D', + calscale => 'GREGORIAN', + 'x-wr-timezone' => 'Europe/London', + source => [ $c->uri_for_action($c->action, [ $c->stash->{uprn} ]), { value => 'URI' } ], + url => $c->uri_for_action('waste/bin_days', [ $c->stash->{uprn} ]), + ); + + my $events = $c->cobrand->bin_future_collections; + my $stamp = DateTime->now->strftime('%Y%m%dT%H%M%SZ'); + foreach (@$events) { + my $event = Data::ICal::Entry::Event->new; + $event->add_properties( + summary => $_->{summary}, + description => $_->{desc}, + dtstamp => $stamp, + dtstart => [ $_->{date}->ymd(''), { value => 'DATE' } ], + dtend => [ $_->{date}->add(days=>1)->ymd(''), { value => 'DATE' } ], + ); + $calendar->add_entry($event); + } + + $c->res->body($calendar->as_string); +} + sub construct_bin_request_form { my $c = shift; -- cgit v1.2.3 From 6337ebfd91a87eaf5efa077c5f9a9eff286c1f76 Mon Sep 17 00:00:00 2001 From: Matthew Somerville Date: Mon, 15 Jun 2020 21:02:37 +0100 Subject: [Bromley] Report missed bin within 2 working days. --- perllib/FixMyStreet/App/Controller/Waste.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'perllib/FixMyStreet/App/Controller/Waste.pm') diff --git a/perllib/FixMyStreet/App/Controller/Waste.pm b/perllib/FixMyStreet/App/Controller/Waste.pm index e606b1d8c..7556781cf 100644 --- a/perllib/FixMyStreet/App/Controller/Waste.pm +++ b/perllib/FixMyStreet/App/Controller/Waste.pm @@ -217,7 +217,7 @@ sub construct_bin_report_form { my $field_list = []; foreach (@{$c->stash->{service_data}}) { - next unless $_->{last}; + next unless $_->{last} && $_->{report_allowed}; my $id = $_->{service_id}; my $name = $_->{service_name}; push @$field_list, "service-$id" => { -- cgit v1.2.3 From d90e1157ee31c374248da0db42b70456c37ddd5b Mon Sep 17 00:00:00 2001 From: Matthew Somerville Date: Fri, 19 Jun 2020 14:56:07 +0100 Subject: [Bromley] Look for open events. --- perllib/FixMyStreet/App/Controller/Waste.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'perllib/FixMyStreet/App/Controller/Waste.pm') diff --git a/perllib/FixMyStreet/App/Controller/Waste.pm b/perllib/FixMyStreet/App/Controller/Waste.pm index 7556781cf..7587795c8 100644 --- a/perllib/FixMyStreet/App/Controller/Waste.pm +++ b/perllib/FixMyStreet/App/Controller/Waste.pm @@ -136,7 +136,7 @@ sub construct_bin_request_form { my $field_list = []; foreach (@{$c->stash->{service_data}}) { - next unless $_->{next}; + next unless $_->{next} && !$_->{request_open}; my $name = $_->{service_name}; my $containers = $_->{request_containers}; my $max = $_->{request_max}; @@ -217,7 +217,7 @@ sub construct_bin_report_form { my $field_list = []; foreach (@{$c->stash->{service_data}}) { - next unless $_->{last} && $_->{report_allowed}; + next unless $_->{last} && $_->{report_allowed} && !$_->{report_open}; my $id = $_->{service_id}; my $name = $_->{service_name}; push @$field_list, "service-$id" => { -- cgit v1.2.3 From 26ca5c069d168eded92b95abd3be0d7b7349b430 Mon Sep 17 00:00:00 2001 From: Matthew Somerville Date: Wed, 24 Jun 2020 22:44:37 +0100 Subject: [Bromley] Push notification from Echo. Make sure a 200 response is always sent for a valid notification. --- perllib/FixMyStreet/App/Controller/Waste.pm | 107 ++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) (limited to 'perllib/FixMyStreet/App/Controller/Waste.pm') diff --git a/perllib/FixMyStreet/App/Controller/Waste.pm b/perllib/FixMyStreet/App/Controller/Waste.pm index 7587795c8..152c7c28e 100644 --- a/perllib/FixMyStreet/App/Controller/Waste.pm +++ b/perllib/FixMyStreet/App/Controller/Waste.pm @@ -11,6 +11,7 @@ use FixMyStreet::App::Form::Waste::AboutYou; use FixMyStreet::App::Form::Waste::Request; use FixMyStreet::App::Form::Waste::Report; use FixMyStreet::App::Form::Waste::Enquiry; +use Open311::GetServiceRequestUpdates; sub auto : Private { my ( $self, $c ) = @_; @@ -457,6 +458,112 @@ sub setup_categories_and_bodies : Private { @$contacts = grep { grep { $_ eq 'Waste' } @{$_->groups} } @$contacts; } +sub receive_echo_event_notification : Path('/waste/echo') : Args(0) { + my ($self, $c) = @_; + $c->stash->{format} = 'xml'; + $c->response->header(Content_Type => 'application/soap+xml'); + + require SOAP::Lite; + + $c->detach('soap_error', [ 'Invalid method', 405 ]) unless $c->req->method eq 'POST'; + + my $echo = $c->cobrand->feature('echo'); + $c->detach('soap_error', [ 'Missing config', 500 ]) unless $echo; + + # Make sure we log entire request for debugging + $c->detach('soap_error', [ 'Missing body' ]) unless $c->req->body; + my $soap = join('', $c->req->body->getlines); + $c->log->info($soap); + + my $body = $c->cobrand->body; + $c->detach('soap_error', [ 'Bad jurisdiction' ]) unless $body; + + my $env = SOAP::Deserializer->deserialize($soap); + + my $header = $env->header; + $c->detach('soap_error', [ 'Missing SOAP header' ]) unless $header; + my $action = $header->{Action}; + $c->detach('soap_error', [ 'Incorrect Action' ]) unless $action && $action eq $echo->{receive_action}; + $header = $header->{Security}; + $c->detach('soap_error', [ 'Missing Security header' ]) unless $header; + my $token = $header->{UsernameToken}; + $c->detach('soap_error', [ 'Authentication failed' ]) + unless $token && $token->{Username} eq $echo->{receive_username} && $token->{Password} eq $echo->{receive_password}; + + my $event = $env->result; + + my $cfg = { echo => Integrations::Echo->new(%$echo) }; + my $request = $c->cobrand->construct_waste_open311_update($cfg, $event); + $request->{updated_datetime} = DateTime::Format::W3CDTF->format_datetime(DateTime->now); + $request->{service_request_id} = $event->{Guid}; + + my $updates = Open311::GetServiceRequestUpdates->new( + system_user => $body->comment_user, + current_body => $body, + ); + + my $p = $updates->find_problem($request); + if ($p) { + $c->forward('check_existing_update', [ $p, $request, $updates ]); + my $comment = $updates->process_update($request, $p); + } + # Still want to say it is okay, even if we did nothing with it + $c->forward('soap_ok'); +} + +sub soap_error : Private { + my ($self, $c, $comment, $code) = @_; + $code ||= 400; + $c->response->status($code); + my $type = $code == 500 ? 'Server' : 'Client'; + $c->response->body(SOAP::Serializer->fault($type, "Bad request: $comment", soap_header())); +} + +sub soap_ok : Private { + my ($self, $c) = @_; + $c->response->status(200); + my $method = SOAP::Data->name("NotifyEventUpdatedResponse")->attr({ + xmlns => "http://www.twistedfish.com/xmlns/echo/api/v1" + }); + $c->response->body(SOAP::Serializer->envelope(method => $method, soap_header())); +} + +sub soap_header { + my $attr = "http://www.twistedfish.com/xmlns/echo/api/v1"; + my $action = "NotifyEventUpdatedResponse"; + my $header = SOAP::Header->name("Action")->attr({ + xmlns => 'http://www.w3.org/2005/08/addressing', + 'soap:mustUnderstand' => 1, + })->value("$attr/ReceiverService/$action"); + + my $dt = DateTime->now(); + my $dt2 = $dt->clone->add(minutes => 5); + my $w3c = DateTime::Format::W3CDTF->new; + my $header2 = SOAP::Header->name("Security")->attr({ + 'soap:mustUnderstand' => 'true', + 'xmlns' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' + })->value( + \SOAP::Header->name( + "Timestamp" => \SOAP::Header->value( + SOAP::Header->name('Created', $w3c->format_datetime($dt)), + SOAP::Header->name('Expires', $w3c->format_datetime($dt2)), + ) + )->attr({ + xmlns => "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", + }) + ); + return ($header, $header2); +} + +sub check_existing_update : Private { + my ($self, $c, $p, $request, $updates) = @_; + + my $cfg = { updates => $updates }; + $c->detach('soap_ok') + unless $c->cobrand->waste_check_last_update( + $cfg, $p, $request->{status}, $request->{external_status_code}); +} + __PACKAGE__->meta->make_immutable; 1; -- cgit v1.2.3 From 0b97468330ab7146f16dfe4060a63d28ce445fa6 Mon Sep 17 00:00:00 2001 From: M Somerville Date: Thu, 12 Nov 2020 11:48:24 +0000 Subject: [Bromley] Use property ID, rather than UPRN. --- perllib/FixMyStreet/App/Controller/Waste.pm | 42 ++++++++++++++--------------- 1 file changed, 21 insertions(+), 21 deletions(-) (limited to 'perllib/FixMyStreet/App/Controller/Waste.pm') diff --git a/perllib/FixMyStreet/App/Controller/Waste.pm b/perllib/FixMyStreet/App/Controller/Waste.pm index 152c7c28e..fe177e9fe 100644 --- a/perllib/FixMyStreet/App/Controller/Waste.pm +++ b/perllib/FixMyStreet/App/Controller/Waste.pm @@ -23,8 +23,8 @@ sub auto : Private { sub index : Path : Args(0) { my ( $self, $c ) = @_; - if (my $uprn = $c->get_param('address')) { - $c->detach('redirect_to_uprn', [ $uprn ]); + if (my $id = $c->get_param('address')) { + $c->detach('redirect_to_id', [ $id ]); } $c->stash->{title} = 'What is your address?'; @@ -58,9 +58,9 @@ sub address_list_form { ); } -sub redirect_to_uprn : Private { - my ($self, $c, $uprn) = @_; - my $uri = '/waste/uprn/' . $uprn; +sub redirect_to_id : Private { + my ($self, $c, $id) = @_; + my $uri = '/waste/' . $id; my $type = $c->get_param('type') || ''; $uri .= '/request' if $type eq 'request'; $uri .= '/report' if $type eq 'report'; @@ -68,20 +68,19 @@ sub redirect_to_uprn : Private { $c->detach; } -sub uprn : Chained('/') : PathPart('waste/uprn') : CaptureArgs(1) { - my ($self, $c, $uprn) = @_; +sub property : Chained('/') : PathPart('waste') : CaptureArgs(1) { + my ($self, $c, $id) = @_; - if ($uprn eq 'missing') { + if ($id eq 'missing') { $c->stash->{template} = 'waste/missing.html'; $c->detach; } $c->forward('/auth/get_csrf_token'); - my $property = $c->stash->{property} = $c->cobrand->call_hook(look_up_property => $uprn); + my $property = $c->stash->{property} = $c->cobrand->call_hook(look_up_property => $id); $c->detach( '/page_error_404_not_found', [] ) unless $property; - $c->stash->{uprn} = $uprn; $c->stash->{latitude} = $property->{latitude}; $c->stash->{longitude} = $property->{longitude}; @@ -89,11 +88,11 @@ sub uprn : Chained('/') : PathPart('waste/uprn') : CaptureArgs(1) { $c->stash->{services} = { map { $_->{service_id} => $_ } @{$c->stash->{service_data}} }; } -sub bin_days : Chained('uprn') : PathPart('') : Args(0) { +sub bin_days : Chained('property') : PathPart('') : Args(0) { my ($self, $c) = @_; } -sub calendar : Chained('uprn') : PathPart('calendar.ics') : Args(0) { +sub calendar : Chained('property') : PathPart('calendar.ics') : Args(0) { my ($self, $c) = @_; $c->res->header(Content_Type => 'text/calendar'); require Data::ICal::RFC7986; @@ -110,8 +109,8 @@ sub calendar : Chained('uprn') : PathPart('calendar.ics') : Args(0) { 'x-published-ttl' => 'P1D', calscale => 'GREGORIAN', 'x-wr-timezone' => 'Europe/London', - source => [ $c->uri_for_action($c->action, [ $c->stash->{uprn} ]), { value => 'URI' } ], - url => $c->uri_for_action('waste/bin_days', [ $c->stash->{uprn} ]), + source => [ $c->uri_for_action($c->action, [ $c->stash->{property}{id} ]), { value => 'URI' } ], + url => $c->uri_for_action('waste/bin_days', [ $c->stash->{property}{id} ]), ); my $events = $c->cobrand->bin_future_collections; @@ -175,7 +174,7 @@ sub construct_bin_request_form { return $field_list; } -sub request : Chained('uprn') : Args(0) { +sub request : Chained('property') : Args(0) { my ($self, $c) = @_; my $field_list = construct_bin_request_form($c); @@ -231,7 +230,7 @@ sub construct_bin_report_form { return $field_list; } -sub report : Chained('uprn') : Args(0) { +sub report : Chained('property') : Args(0) { my ($self, $c) = @_; my $field_list = construct_bin_report_form($c); @@ -266,7 +265,7 @@ sub process_report_data : Private { return 1; } -sub enquiry : Chained('uprn') : Args(0) { +sub enquiry : Chained('property') : Args(0) { my ($self, $c) = @_; if (my $template = $c->get_param('template')) { @@ -279,18 +278,18 @@ sub enquiry : Chained('uprn') : Args(0) { my $category = $c->get_param('category'); my $service = $c->get_param('service_id'); if (!$category || !$service || !$c->stash->{services}{$service}) { - $c->res->redirect('/waste/uprn/' . $c->stash->{uprn}); + $c->res->redirect('/waste/' . $c->stash->{property}{id}); $c->detach; } my ($contact) = grep { $_->category eq $category } @{$c->stash->{contacts}}; if (!$contact) { - $c->res->redirect('/waste/uprn/' . $c->stash->{uprn}); + $c->res->redirect('/waste/' . $c->stash->{property}{id}); $c->detach; } my $field_list = []; foreach (@{$contact->get_metadata_for_input}) { - next if $_->{code} eq 'service_id' || $_->{code} eq 'uprn'; + next if $_->{code} eq 'service_id' || $_->{code} eq 'uprn' || $_->{code} eq 'property_id'; my $type = 'Text'; $type = 'TextArea' if 'text' eq ($_->{datatype} || ''); my $required = $_->{required} eq 'true' ? 1 : 0; @@ -421,7 +420,8 @@ sub add_report : Private { $c->set_param('category', $data->{category}); $c->set_param('title', $data->{title}); $c->set_param('detail', $data->{detail}); - $c->set_param('uprn', $c->stash->{uprn}); + $c->set_param('uprn', $c->stash->{property}{uprn}); + $c->set_param('property_id', $c->stash->{property}{id}); $c->forward('setup_categories_and_bodies') unless $c->stash->{contacts}; $c->forward('/report/new/non_map_creation', [['/waste/remove_name_errors']]) or return; -- cgit v1.2.3