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

CDPS-1167: Implementing proxy request to update smoker status #17

Merged
merged 2 commits into from
Feb 7, 2025
Merged
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
1 change: 1 addition & 0 deletions helm_deploy/values-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ generic-service:
APPLICATIONINSIGHTS_CONFIGURATION_FILE: "applicationinsights.dev.json"
API_HMPPS_AUTH_BASE_URL: "https://sign-in-dev.hmpps.service.justice.gov.uk/auth"
API_PRISONER_SEARCH_BASE_URL: "https://prisoner-search-dev.prison.service.justice.gov.uk"
PRISON_API_BASE_URL: "https://prison-api-dev.prison.service.justice.gov.uk"

scheduledDowntime:
enabled: true
Expand Down
1 change: 1 addition & 0 deletions helm_deploy/values-preprod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ generic-service:
APPLICATIONINSIGHTS_CONFIGURATION_FILE: "applicationinsights.dev.json"
API_HMPPS_AUTH_BASE_URL: "https://sign-in-preprod.hmpps.service.justice.gov.uk/auth"
API_PRISONER_SEARCH_BASE_URL: "https://prisoner-search-preprod.prison.service.justice.gov.uk"
PRISON_API_BASE_URL: "https://prison-api-preprod.prison.service.justice.gov.uk"

# CloudPlatform AlertManager receiver to route prometheus alerts to slack
# See https://user-guide.cloud-platform.service.justice.gov.uk/documentation/monitoring-an-app/how-to-create-alarms.html#creating-your-own-custom-alerts
Expand Down
1 change: 1 addition & 0 deletions helm_deploy/values-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ generic-service:
env:
API_HMPPS_AUTH_BASE_URL: "https://sign-in.hmpps.service.justice.gov.uk/auth"
API_PRISONER_SEARCH_BASE_URL: "https://prisoner-search.prison.service.justice.gov.uk"
PRISON_API_BASE_URL: "https://prison-api.prison.service.justice.gov.uk"

postgresDatabaseRestore:
enabled: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package uk.gov.justice.digital.hmpps.healthandmedication.annotation

import jakarta.validation.Constraint
import jakarta.validation.Payload
import uk.gov.justice.digital.hmpps.healthandmedication.validator.ReferenceDataValidator
import uk.gov.justice.digital.hmpps.healthandmedication.validator.ReferenceDataListValidator
import kotlin.reflect.KClass

@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Constraint(validatedBy = [ReferenceDataValidator::class])
@Constraint(validatedBy = [ReferenceDataListValidator::class])
annotation class ReferenceDataCode(
val domains: Array<String> = [],
val message: String = "The value must be a reference domain code id of the correct domain, null, or Undefined.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package uk.gov.justice.digital.hmpps.healthandmedication.annotation

import jakarta.validation.Constraint
import jakarta.validation.Payload
import uk.gov.justice.digital.hmpps.healthandmedication.validator.ReferenceDataListValidator
import kotlin.reflect.KClass

@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Constraint(validatedBy = [ReferenceDataListValidator::class])
annotation class ReferenceDataListValidation(
val domains: Array<String> = [],
val message: String = "The supplied array must either be empty or contain reference data codes of the correct domain.",
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = [],
)
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import kotlin.reflect.KClass
@Constraint(validatedBy = [ReferenceDataValidator::class])
annotation class ReferenceDataValidation(
val domains: Array<String> = [],
val message: String = "The supplied array must either be empty or contain reference data codes of the correct domain.",
val message: String = "The supplied code must either be null or match a valid reference data code of the correct domain.",
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = [],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package uk.gov.justice.digital.hmpps.healthandmedication.client.prisonapi

import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.client.WebClient
import uk.gov.justice.digital.hmpps.healthandmedication.client.prisonapi.request.PrisonApiSmokerStatusUpdate
import uk.gov.justice.digital.hmpps.healthandmedication.config.DownstreamServiceException

@Component
class PrisonApiClient(@Qualifier("prisonApiWebClient") private val webClient: WebClient) {
fun updateSmokerStatus(offenderNo: String, updateSmokerStatus: PrisonApiSmokerStatusUpdate) = try {
webClient
.put()
.uri("/api/offenders/{offenderNo}/smoker", offenderNo)
.bodyValue(updateSmokerStatus)
.retrieve()
.toBodilessEntity()
.block()
} catch (e: Exception) {
throw DownstreamServiceException("Update smoker status request failed", e)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package uk.gov.justice.digital.hmpps.healthandmedication.client.prisonapi.request

import io.swagger.v3.oas.annotations.media.Schema

enum class PrisonApiSmokerStatus {
Y,
N,
V,
}

@Schema(description = "Update to prisoner's smoker status")
data class PrisonApiSmokerStatusUpdate(
@Schema(
description = "The smoker status code ('Y' for 'Yes', 'N' for 'No', 'V' for 'Vaper/NRT Only')",
example = "Y",
allowableValues = ["Y", "N", "V"],
required = true,
nullable = true,
)
val smokerStatus: PrisonApiSmokerStatus?,
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.reactive.function.client.WebClientResponseException.NotFound
import uk.gov.justice.digital.hmpps.healthandmedication.client.prisonersearch.dto.PrisonerDto
import uk.gov.justice.digital.hmpps.healthandmedication.client.prisonersearch.dto.PrisonerSearchResultDto
import uk.gov.justice.digital.hmpps.healthandmedication.client.prisonersearch.response.PrisonerDto
import uk.gov.justice.digital.hmpps.healthandmedication.client.prisonersearch.response.PrisonerSearchResultDto
import uk.gov.justice.digital.hmpps.healthandmedication.config.DownstreamServiceException

@Component
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package uk.gov.justice.digital.hmpps.healthandmedication.client.prisonersearch.dto
package uk.gov.justice.digital.hmpps.healthandmedication.client.prisonersearch.response

data class PrisonerDto(
val prisonerNumber: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package uk.gov.justice.digital.hmpps.healthandmedication.client.prisonersearch.dto
package uk.gov.justice.digital.hmpps.healthandmedication.client.prisonersearch.response

data class PrisonerSearchResultDto(
val content: List<PrisonerDto>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ class HealthAndMedicationExceptionHandler {
class HealthAndMedicationDataNotFoundException(prisonerNumber: String) : Exception("No data for '$prisonerNumber'")
class ReferenceDataDomainNotFoundException(code: String) : Exception("No data for domain '$code'")
class ReferenceDataCodeNotFoundException(code: String, domain: String) : Exception("No data for code '$code' in domain '$domain'")

class GenericNotFoundException(message: String) : Exception(message)
class IllegalFieldHistoryException(prisonerNumber: String) : Exception("Cannot update field history for prisoner: '$prisonerNumber'")

class DownstreamServiceException(message: String, cause: Throwable) : Exception(message, cause)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package uk.gov.justice.digital.hmpps.personintegrationapi.config

import org.springframework.http.HttpMethod
import org.springframework.http.RequestEntity
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequestEntityConverter
import org.springframework.util.MultiValueMap
import java.util.Objects

@Suppress("UNCHECKED_CAST")
class UserEnhancedOAuth2ClientCredentialGrantRequestConverter : OAuth2ClientCredentialsGrantRequestEntityConverter() {

fun enhanceWithUsername(
grantRequest: OAuth2ClientCredentialsGrantRequest?,
username: String?,
): RequestEntity<Any> {
val request = super.convert(grantRequest)
val headers = request.headers
val body = Objects.requireNonNull(request).body
val formParameters = body as MultiValueMap<String, Any>
if (username != null) {
formParameters.add("username", username)
}
return RequestEntity(formParameters, headers, HttpMethod.POST, request.url)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,19 @@ package uk.gov.justice.digital.hmpps.healthandmedication.config
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager
import org.springframework.security.oauth2.client.InMemoryOAuth2AuthorizedClientService
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService
import org.springframework.security.oauth2.client.endpoint.DefaultClientCredentialsTokenResponseClient
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository
import org.springframework.web.context.annotation.RequestScope
import org.springframework.web.reactive.function.client.WebClient
import org.springframework.web.reactive.function.client.WebClient.Builder
import uk.gov.justice.digital.hmpps.personintegrationapi.config.UserEnhancedOAuth2ClientCredentialGrantRequestConverter
import uk.gov.justice.hmpps.kotlin.auth.authorisedWebClient
import uk.gov.justice.hmpps.kotlin.auth.healthWebClient
import java.time.Duration
Expand All @@ -18,18 +28,60 @@ class WebClientConfiguration(
@Value("\${api.prisoner-search.base-url}") private val prisonerSearchBaseUri: String,
@Value("\${api.prisoner-search.timeout:30s}") private val prisonerSearchTimeout: Duration,
@Value("\${api.prisoner-search.health-timeout:20s}") private val prisonerSearchHealthTimeout: Duration,

@Value("\${api.prison-api.base-url}") private val prisonApiBaseUri: String,
@Value("\${api.prison-api.timeout:30s}") private val prisonApiTimeout: Duration,
@Value("\${api.prison-api.health-timeout:20s}") private val prisonApiHealthTimeout: Duration,
) {
@Bean
fun hmppsAuthHealthWebClient(builder: Builder): WebClient = builder.healthWebClient(hmppsAuthBaseUri, hmppsAuthHealthTimeout)

@Bean
fun prisonerSearchHealthWebClient(builder: Builder) = builder.healthWebClient(prisonerSearchBaseUri, prisonerSearchHealthTimeout)

@Bean
fun prisonApiHealthWebClient(builder: Builder): WebClient = builder.healthWebClient(prisonApiBaseUri, prisonApiHealthTimeout)

@Bean
fun prisonerSearchWebClient(authorizedClientManager: OAuth2AuthorizedClientManager, builder: Builder) = builder.authorisedWebClient(
authorizedClientManager,
"hmpps-health-and-medication-api",
prisonerSearchBaseUri,
prisonerSearchTimeout,
)

@Bean
@RequestScope
fun prisonApiWebClient(
clientRegistrationRepository: ClientRegistrationRepository,
builder: Builder,
) = builder.authorisedWebClient(
authorizedClientManagerUserEnhanced(clientRegistrationRepository),
"hmpps-health-and-medication-api",
prisonApiBaseUri,
prisonApiTimeout,
)

private fun authorizedClientManagerUserEnhanced(clients: ClientRegistrationRepository?): OAuth2AuthorizedClientManager {
val service: OAuth2AuthorizedClientService = InMemoryOAuth2AuthorizedClientService(clients)
val manager = AuthorizedClientServiceOAuth2AuthorizedClientManager(clients, service)

val defaultClientCredentialsTokenResponseClient = DefaultClientCredentialsTokenResponseClient()
val authentication = SecurityContextHolder.getContext().authentication
defaultClientCredentialsTokenResponseClient.setRequestEntityConverter { grantRequest: OAuth2ClientCredentialsGrantRequest ->
val converter = UserEnhancedOAuth2ClientCredentialGrantRequestConverter()
converter.enhanceWithUsername(grantRequest, authentication.name)
}

val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.clientCredentials { clientCredentialsGrantBuilder: OAuth2AuthorizedClientProviderBuilder.ClientCredentialsGrantBuilder ->
clientCredentialsGrantBuilder.accessTokenResponseClient(
defaultClientCredentialsTokenResponseClient,
)
}
.build()

manager.setAuthorizedClientProvider(authorizedClientProvider)
return manager
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,9 @@ import uk.gov.justice.hmpps.kotlin.health.HealthPingCheck

@Component("hmppsAuth")
class HealthPingCheck(@Qualifier("hmppsAuthHealthWebClient") webClient: WebClient) : HealthPingCheck(webClient)

@Component("prisonerSearch")
class PrisonerSearchHealthPing(@Qualifier("prisonerSearchHealthWebClient") webClient: WebClient) : HealthPingCheck(webClient)

@Component("prisonApi")
class PrisonApiHealthPing(@Qualifier("prisonApiHealthWebClient") webClient: WebClient) : HealthPingCheck(webClient)
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ import jakarta.persistence.OneToMany
import jakarta.persistence.Table
import org.hibernate.Hibernate
import org.hibernate.annotations.SortNatural
import uk.gov.justice.digital.hmpps.healthandmedication.dto.response.DietAndAllergyDto
import uk.gov.justice.digital.hmpps.healthandmedication.dto.response.HealthDto
import uk.gov.justice.digital.hmpps.healthandmedication.dto.response.ReferenceDataSelection
import uk.gov.justice.digital.hmpps.healthandmedication.dto.response.ValueWithMetadata
import uk.gov.justice.digital.hmpps.healthandmedication.enums.HealthAndMedicationField
import uk.gov.justice.digital.hmpps.healthandmedication.enums.HealthAndMedicationField.FOOD_ALLERGY
import uk.gov.justice.digital.hmpps.healthandmedication.enums.HealthAndMedicationField.MEDICAL_DIET
import uk.gov.justice.digital.hmpps.healthandmedication.enums.HealthAndMedicationField.PERSONALISED_DIET
import uk.gov.justice.digital.hmpps.healthandmedication.mapper.toSimpleDto
import uk.gov.justice.digital.hmpps.healthandmedication.resource.dto.response.DietAndAllergyResponse
import uk.gov.justice.digital.hmpps.healthandmedication.resource.dto.response.HealthAndMedicationResponse
import uk.gov.justice.digital.hmpps.healthandmedication.resource.dto.response.ReferenceDataSelection
import uk.gov.justice.digital.hmpps.healthandmedication.resource.dto.response.ValueWithMetadata
import java.time.ZonedDateTime
import java.util.SortedSet
import kotlin.reflect.KMutableProperty0
Expand Down Expand Up @@ -56,9 +56,9 @@ class PrisonerHealth(
PERSONALISED_DIET to ::personalisedDietaryRequirements,
)

fun toHealthDto(): HealthDto = HealthDto(toDietAndAllergyDto())
fun toHealthDto(): HealthAndMedicationResponse = HealthAndMedicationResponse(toDietAndAllergyDto())

fun toDietAndAllergyDto(): DietAndAllergyDto = DietAndAllergyDto(
fun toDietAndAllergyDto(): DietAndAllergyResponse = DietAndAllergyResponse(
foodAllergies = getReferenceDataListValueWithMetadata(
foodAllergies,
{ allergies ->
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package uk.gov.justice.digital.hmpps.healthandmedication.mapper

import uk.gov.justice.digital.hmpps.healthandmedication.dto.ReferenceDataCodeDto
import uk.gov.justice.digital.hmpps.healthandmedication.dto.ReferenceDataSimpleDto
import uk.gov.justice.digital.hmpps.healthandmedication.jpa.ReferenceDataCode
import uk.gov.justice.digital.hmpps.healthandmedication.resource.dto.ReferenceDataCodeDto
import uk.gov.justice.digital.hmpps.healthandmedication.resource.dto.ReferenceDataSimpleDto
import java.time.ZonedDateTime

fun ReferenceDataCode.toDto(): ReferenceDataCodeDto = ReferenceDataCodeDto(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package uk.gov.justice.digital.hmpps.healthandmedication.mapper

import uk.gov.justice.digital.hmpps.healthandmedication.dto.ReferenceDataDomainDto
import uk.gov.justice.digital.hmpps.healthandmedication.jpa.ReferenceDataDomain
import uk.gov.justice.digital.hmpps.healthandmedication.resource.dto.ReferenceDataDomainDto
import java.time.ZonedDateTime

fun ReferenceDataDomain.toDto(): ReferenceDataDomainDto = ReferenceDataDomainDto(
Expand Down
Loading