Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BANES][Open311] Create a multi with a Passthrough #399

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions conf/council-www.banes.gov.uk.yml-example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
endpoint: https://localhost:4000/
api_key: 123
bearer_details:
username: FMS
password: FMSPassword
url: url/address
46 changes: 23 additions & 23 deletions perllib/Open311/Endpoint.pm
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,15 @@ sub check_jurisdiction_id {
* jurisdictions - an array of jurisdiction_ids
you may want to subclass the methods:
- requires_jurisdiction_ids
- check_jurisdiction_id
- check_jurisdiction_id
* default_identifier_type
Open311 doesn't mandate what these types look like, but a backend
server may! The module provides an example identifier type which allows
ascii "word" characters .e.g [a-zA-Z0-9_] as an example default.
You can also override these individually using:

identifier_types => {
api_key => '//str', #
api_key => '//str', #
jurisdiction_id => ...
service_code => ...
service_request_id => ...
Expand Down Expand Up @@ -246,9 +246,9 @@ has json => (
has xml => (
is => 'lazy',
default => sub {
XML::Simple->new(
NoAttr=> 1,
KeepRoot => 1,
XML::Simple->new(
NoAttr=> 1,
KeepRoot => 1,
SuppressEmpty => 0,
);
},
Expand Down Expand Up @@ -419,15 +419,15 @@ sub GET_Service_Definition {
%optional,
$attribute->has_values ? (
values => [
map {
map {
my ($key, $name) = @$_;
+{
key => $key,
+{
key => $key,
name => $name,
}
} $self->service_attribute_values( $attribute )
]) : (),
map { $_ => $attribute->$_ }
map { $_ => $attribute->$_ }
qw/ code datatype datatype_description description /,
}
} $service->get_attributes,
Expand Down Expand Up @@ -456,8 +456,8 @@ sub POST_Service_Request_input_schema {
# to give a nice error message
return {
type => '//rec',
required => {
service_code => $self->get_identifier_type('service_code'),
required => {
service_code => $self->get_identifier_type('service_code'),
api_key => $self->get_identifier_type('api_key') },
rest => '//any',
};
Expand Down Expand Up @@ -518,7 +518,7 @@ sub POST_Service_Request_input_schema {
};
}

return {
return {
type => '//any',
of => \@address_schemas,
};
Expand Down Expand Up @@ -571,12 +571,12 @@ sub POST_Service_Request {
}

my @service_requests = $self->post_service_request( $service, $args );

return {
service_requests => [
map {
my $service_notice =
$_->service_notice
my $service_notice =
$_->service_notice
|| $service->default_service_notice
|| $self->default_service_notice;
+{
Expand Down Expand Up @@ -754,7 +754,7 @@ sub format_service_requests {
zipcode
lat
long
/
/
),
(
map {
Expand Down Expand Up @@ -812,11 +812,11 @@ sub get_jurisdiction_id_validation {

# jurisdiction_id is documented as "Required", but with the note
# 'This is only required if the endpoint serves multiple jurisdictions'
# i.e. it is optional as regards the schema, but the server may choose
# i.e. it is optional as regards the schema, but the server may choose
# to error if it is not provided.
return {
type => '//rec',
($self->requires_jurisdiction_ids ? 'required' : 'optional') => {
($self->requires_jurisdiction_ids ? 'required' : 'optional') => {
jurisdiction_id => $self->get_identifier_type('jurisdiction_id'),
},
};
Expand All @@ -834,7 +834,7 @@ sub get_jurisdiction_id_optional_clause {

sub call_api {
my ($self, $api_name, @args) = @_;

my $api_method = $self->can($api_name)
or die "No such API $api_name!";

Expand Down Expand Up @@ -894,10 +894,10 @@ sub format_response {
my $data = $response->data;
if ($ext eq 'json') {
return [
$status,
$status,
[ 'Content-Type' => 'application/json' ],
[ $self->json->encode(
$self->spark->process_for_json( $data )
[ $self->json->encode(
$self->spark->process_for_json( $data )
)]
];
}
Expand All @@ -916,7 +916,7 @@ sub format_response {
404,
[ 'Content-Type' => 'text/plain' ],
[ 'Bad extension. We support .xml and .json' ],
]
]
}
}
}
Expand Down
9 changes: 7 additions & 2 deletions perllib/Open311/Endpoint/Integration/Passthrough.pm
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,18 @@ sub _request {
$params->{api_key} = $self->api_key;
$self->logger->debug($url);
$self->logger->dump($params);
$resp = $self->ua->post($url, $params);
my $auth = delete $params->{'Authorization'};
if ($auth) {
$resp = $self->ua->post($url, 'Authorization' => $auth, 'Content' => $params->{Content});
} else {
$resp = $self->ua->post($url, $params);
}
} else {
$url->query_form(%$params);
$self->logger->debug($url);
$resp = $self->ua->get($url);
}
my $content = $resp->decoded_content;
my $content = $resp->decoded_content( charset => $url =~ /bathnes/ ? 'utf-8' : undef );
$self->logger->debug($content);
my $xml = $self->pt_xml->XMLin(\$content);
die $xml->{error}[0]->{description} . "\n" if $xml->{error};
Expand Down
3 changes: 2 additions & 1 deletion perllib/Open311/Endpoint/Integration/UK.pm
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ has '+identifier_types' => (
return {
jurisdiction_id => Open311::Endpoint::Schema->enum('//str', @$ids),
# some service codes have spaces, ampersands, commas, etc
service_code => { type => '/open311/regex', pattern => qr/^ [&,\.\w_\- \/\(\)]+ $/ax },
# & banes uses email addresses as contacts for their passthrough, so we need @
service_code => { type => '/open311/regex', pattern => qr/^ [&,\.\w_\- \@\/\(\)]+ $/ax },
# some request IDs include slashes
service_request_id => { type => '/open311/regex', pattern => qr/^ [\w_\-\/]+ $/ax },
# one backend, service codes have colons in
Expand Down
66 changes: 59 additions & 7 deletions perllib/Open311/Endpoint/Integration/UK/BANES.pm
Original file line number Diff line number Diff line change
@@ -1,12 +1,64 @@
=head1 NAME

Open311::Endpoint::Integration::UK::BANES - Bath and North East Somerset integration set-up

=head1 SYNOPSIS

BANES manage their own Open311 server to receive all reports made on FMS, whether in
email categories or in those created by their Confirm integration. The Confirm
integration only receives the reports in categories in its services.

=cut

package Open311::Endpoint::Integration::UK::BANES;

use Moo;
extends 'Open311::Endpoint::Integration::Confirm';
extends 'Open311::Endpoint::Integration::Multi';

use Module::Pluggable
search_path => ['Open311::Endpoint::Integration::UK::BANES'],
instantiate => 'new';

has jurisdiction_id => (
is => 'ro',
default => 'banes',
);

=head2 _map_with_new_id & _map_from_new_id

BANES's two endpoints do not overlap in service_codes as the Passthrough
accepts all reports with email addresses and the others are Confirm.
This code overrides the default Multi code to not change anything,
and cope accordingly.

=cut

sub _map_with_new_id {
my ($self, $attributes, @results) = @_;

@results = map {
my ($name, $result) = @$_;
$result;
} @results;

return @results;
}

my $email_regex = qr/\@.*?\./;

sub _map_from_new_id {
my ($self, $code, $type) = @_;

my $integration;
if ($type eq 'service' || $type eq 'request') {
Copy link
Member

@dracos dracos Mar 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this will work, as the code will be a number when type is request for either Passthrough or Confirm

if ($code =~ /$email_regex/) {
$integration = 'Passthrough';
} else {
$integration = 'Confirm';
}
}

around BUILDARGS => sub {
my ($orig, $class, %args) = @_;
$args{jurisdiction_id} = 'banes_confirm';
return $class->$orig(%args);
};
return ($integration, $code);
}

1;
__PACKAGE__->run_if_script;
12 changes: 12 additions & 0 deletions perllib/Open311/Endpoint/Integration/UK/BANES/Confirm.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package Open311::Endpoint::Integration::UK::BANES::Confirm;

use Moo;
extends 'Open311::Endpoint::Integration::Confirm';

around BUILDARGS => sub {
my ($orig, $class, %args) = @_;
$args{jurisdiction_id} = 'banes_confirm';
return $class->$orig(%args);
};

1;
109 changes: 109 additions & 0 deletions perllib/Open311/Endpoint/Integration/UK/BANES/Passthrough.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
=head1 NAME

Open311::Endpoint::Integration::UK::BANES::Passthrough - Bath and North East Somerset Passthrough backend

=head1 SUMMARY

This is the BANES-specific Passthrough integration. It follows Open311
standards except requires a bearer token to be passed rather than an
api token

=cut

package Open311::Endpoint::Integration::UK::BANES::Passthrough;

use Moo;
extends 'Open311::Endpoint::Integration::Passthrough';

use Types::Standard ':all';

around BUILDARGS => sub {
my ($orig, $class, %args) = @_;
$args{jurisdiction_id} = 'www.banes.gov.uk';
return $class->$orig(%args);
};

has bearer_details => (is => 'ro');

=head2 identifier_types

Add an identifier_types attribute to duplicate the UK.pm service_code validation
so the mocking in the test can remain shallow

=cut

has '+identifier_types' => (
is => 'lazy',
isa => HashRef[Any],
default => sub {
return {
service_code => { type => '/open311/regex', pattern => qr/^ [&,\.\w_\- \@\/\(\)]+ $/ax },
};
}
);

=head2 service

BANES are not providing services or service calls for their own open311 backend.
All service requests should be sent, so we generate an artificial service
that permits the service request to be sent, but maintains the open311 flow.

=cut

sub service {
my ($self, $service_id, $args) = @_;

my $service = Open311::Endpoint::Service->new(service_code => $service_id);

my $attribute = Open311::Endpoint::Service::Attribute->new(
code => $service_id,
datatype => 'string',
);
push @{ $service->attributes }, $attribute;

return $service;
};

=head2 _request

Rather than using an api key, we need to get a bearer token for authorisation
and set the Bearer header.

Also munge params - jurisdiction_id is not expected and we want to make
the service code the same as the Confirm code now it's been established
it's being sent to the Passthrough

=cut

around _request => sub {
my ($orig, $self, $method, $url, $params) = @_;

delete $params->{jurisdiction_id};

if ($params->{service_code} =~ /passthrough-.*?@/) {
($params->{service_code}) = $params->{service_code} =~ /passthrough-(.*?)@/;
}

if ($method eq 'POST' && $url !~ /api\/token/ ) {
$params = { 'Content' => $params, 'Authorization' => 'Bearer ' . $self->_get_bearer_token()->content };
};

return $self->$orig($method, $url, $params);
};

sub _get_bearer_token {
my $self = shift;

my $bearer_details = $self->bearer_details;

my $params = {
username => $bearer_details->{username},
password => $bearer_details->{password},
};

my $url = $bearer_details->{url};

return $self->ua->post($url, $params);
};

1;
4 changes: 2 additions & 2 deletions perllib/Open311/Endpoint/Service/Attribute.pm
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ has code => (
has variable => (
is => 'ro',
isa => Bool,
default => sub { 1 },
default => sub { 1 },
);

# Denotes the type of field used for user input.
Expand All @@ -41,7 +41,7 @@ has datatype_description => (
);

# A description of the attribute field with instructions for the user to find
# and identify the requested information
# and identify the requested information
has description => (
is => 'ro',
isa => Str,
Expand Down
Loading