Skip to content

Commit

Permalink
MAP-1665 Migrate to the new Alerts API (#2363)
Browse files Browse the repository at this point in the history
* Introduce Alerts API interface

NOTE: base class currently duplicates the
NomisClient::Base - refactor / DRY up

* Migrate Alerts to use the new AlertsApiClient

* DRY up the Oauth client

* Update the NomisAlerts Importer

* Prefer HMPPS over DPS for the oauth token

* Add ALERTS_API_BASE_URL to the test environment

* Fix specs to support the new AlertsApi::Client

* Generate wiremock responses for the AlertsApiClient

* Resolve Wiremock `__files` not found on CI

* Add:  ALERTS_API_BASE_URL: alerts_api_base_url_key

* Add ALERTS_API_BASE_URL to .env.example
  • Loading branch information
tobyprivett authored Oct 24, 2024
1 parent b3e4d93 commit 7045f7e
Show file tree
Hide file tree
Showing 63 changed files with 584 additions and 744 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ NOMIS_SITE_FOR_API='https://nomis-api.example.com'
NOMIS_AUTH_SCHEME='basic_auth'
NOMIS_SITE_FOR_AUTH='https://nomis-api.example.com/auth'
NOMIS_PRISON_API_PATH_PREFIX='/api'
ALERTS_API_BASE_URL=https://alerts-api-dev.hmpps.service.justice.gov.uk/
SENTRY_DSN='https://sentry.example.com/'

ENCRYPTOR_SALT="\x97\x19\xBF\xF6\xC2\x9DZ\x92D\xFFq\xFC\x9B\x1C\xC4\t\x95\xBA\xBF\xF7q<\xC9\xFC\x81\xBD;\xDEtH\xA4\xB2"
Expand Down
1 change: 1 addition & 0 deletions .env.test
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ NOMIS_SITE_FOR_API=http://localhost:8888
NOMIS_PRISON_API_PATH_PREFIX='/api'
NOMIS_SITE_FOR_AUTH='http://localhost:8888/auth'
SENTRY_DSN='https://sentry.example.com/'
ALERTS_API_BASE_URL=http://localhost:8888
4 changes: 4 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
"description": "Name of the storage bucket to use on AWS S3",
"required": true
},
"ALERTS_API_BASE_URL": {
"description": "Base URL for Alerts API",
"required": true
},
"NOMIS_SITE_FOR_API": {
"description": "Base URL for NOMIS API",
"required": true
Expand Down
32 changes: 32 additions & 0 deletions app/lib/alerts_api_client/alert_types.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# frozen_string_literal: true

module AlertsApiClient
class AlertTypes < AlertsApiClient::Base
class << self
def get
fetch_response.map { |alert_type|
alert_type['alertCodes'].map do |alert_code|
attributes_for(alert_type, alert_code)
end
}.flatten
end

def fetch_response
AlertsApiClient::Base.get(
'alert-types',
headers: { 'Page-Limit' => '1000' },
).parsed
end

def attributes_for(alert_type, alert_code)
{
code: alert_code['code'],
type_code: alert_type['code'],
description: alert_code['description'],
type_description: alert_type['description'],
active_flag: alert_type['isActive'],
}
end
end
end
end
35 changes: 35 additions & 0 deletions app/lib/alerts_api_client/alerts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

module AlertsApiClient
class Alerts < AlertsApiClient::Base
class << self
def get(prison_number)
JSON.parse(fetch_response(prison_number).body)['content'].map do |alert|
attributes_for(alert)
end
end

def fetch_response(prison_number)
AlertsApiClient::Base.get(
"/prisoners/#{prison_number}/alerts?isActive=true",
)
end

def attributes_for(alert)
{
alert_id: alert['alertUuid'],
alert_type: alert['alertCode']['alertTypeCode'],
alert_type_description: alert['alertCode']['alertTypeDescription'],
alert_code: alert['alertCode']['code'],
alert_code_description: alert['alertCode']['description'],
comment: (alert['comments'] || []).join('. '),
created_at: alert['createdAt'],
expires_at: alert['activeTo'],
expired: !alert['isActive'],
active: alert['isActive'],
prison_number: alert['prisonNumber'],
}
end
end
end
end
17 changes: 17 additions & 0 deletions app/lib/alerts_api_client/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

module AlertsApiClient
class Base < HmppsApiClient
class << self
protected

def site_for_api
ENV['ALERTS_API_BASE_URL']
end

def token_request_path_prefix
''
end
end
end
end
108 changes: 108 additions & 0 deletions app/lib/hmpps_api_client.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# frozen_string_literal: true

class HmppsApiClient
HMPPS_TIMEOUT = 10 # in seconds

class << self
def get(path, params = {})
token_request(:get, path, params)
end

def post(path, params = {})
params = update_json_headers(params)
token_request(:post, path, params)
end

def put(path, params = {})
params = update_json_headers(params)
token_request(:put, path, params)
end

protected

def site_for_api
# expect a value in the subclass
end

def token_request_path_prefix
# expect a value in the subclass
end

private

REFRESH_TOKEN_TIMEFRAME_IN_SECONDS = 5

def token_request(method, path, params)
token.send(method, "#{token_request_path_prefix}#{path}", params)
rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
Rails.logger.warn "HMPPS Connection Error: #{e.message}"
raise e
rescue OAuth2::Error => e
Rails.logger.warn "HMPPS OAuth Client Error: #{e.message}"
raise e
end

def token
return @token if @token && !token_expired_or_to_expire?

@token = client.client_credentials.get_token
end

def client
@client ||= OAuth2::Client.new(
ENV['NOMIS_CLIENT_ID'],
ENV['NOMIS_CLIENT_SECRET'],
site: site_for_api,
auth_scheme: ENV['NOMIS_AUTH_SCHEME'],
token_url: "#{ENV['NOMIS_SITE_FOR_AUTH']}/oauth/token",
raise_errors: true,
connection_opts: { request: { timeout: HMPPS_TIMEOUT, open_timeout: HMPPS_TIMEOUT } },
)
end

def token_expired_or_to_expire?
# rubocop:disable Rails/TimeZone
@token.expires? &&
(@token.expires_at - REFRESH_TOKEN_TIMEFRAME_IN_SECONDS < Time.now.to_i)
# rubocop:enable Rails/TimeZone
end

def update_json_headers(params)
return unless params

{
headers:
{
'Accept': 'application/json',
'Content-Type': 'application/json',
},
}.deep_merge(params)
end

def sentry_extra(path, params, exception)
response = exception.response

message = begin
ActiveSupport::JSON.decode(response.body)['developerMessage']
rescue StandardError
nil
end

{
route: path,
body_params: params,
nomis_response_status: response.status,
nomis_response_body: response.body,
nomis_response_message: message,
}
end

def log_exception(description, path, params, exception)
Sentry.capture_message(
description,
extra: sentry_extra(path, params, exception),
level: 'error',
)
end
end
end
3 changes: 0 additions & 3 deletions app/lib/nomis_client.rb

This file was deleted.

31 changes: 0 additions & 31 deletions app/lib/nomis_client/alert_codes.rb

This file was deleted.

35 changes: 0 additions & 35 deletions app/lib/nomis_client/alert_types.rb

This file was deleted.

36 changes: 0 additions & 36 deletions app/lib/nomis_client/alerts.rb

This file was deleted.

Loading

0 comments on commit 7045f7e

Please sign in to comment.