Skip to content

Commit 15da2c3

Browse files
authored
Merge branch 'main' into PI-2517-refactor-tests-in-external-api
2 parents f6282a6 + fa4d817 commit 15da2c3

26 files changed

+725
-27
lines changed

build.gradle.kts

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ dependencies {
2727
testImplementation("org.wiremock:wiremock-standalone:3.2.0")
2828
testImplementation("io.kotest.extensions:kotest-extensions-spring:1.1.3")
2929
testImplementation("org.mockito:mockito-core:5.7.0")
30+
testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.17.2")
3031

3132
annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
3233
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.controllers.v1
2+
3+
import ReferenceData
4+
import io.swagger.v3.oas.annotations.Hidden
5+
import io.swagger.v3.oas.annotations.Operation
6+
import io.swagger.v3.oas.annotations.media.Content
7+
import io.swagger.v3.oas.annotations.media.ExampleObject
8+
import io.swagger.v3.oas.annotations.media.Schema
9+
import io.swagger.v3.oas.annotations.responses.ApiResponse
10+
import org.springframework.boot.context.properties.EnableConfigurationProperties
11+
import org.springframework.web.bind.annotation.GetMapping
12+
import org.springframework.web.bind.annotation.RequestMapping
13+
import org.springframework.web.bind.annotation.RestController
14+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.config.AuthorisationConfig
15+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Response
16+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.ReferenceDataService
17+
18+
@Hidden
19+
@RestController
20+
@EnableConfigurationProperties(AuthorisationConfig::class)
21+
@RequestMapping("/v1/hmpps/reference-data")
22+
class ReferenceDataController(
23+
var referenceDataService: ReferenceDataService,
24+
) {
25+
@GetMapping
26+
@Operation(
27+
summary = """
28+
Returns probation and prison reference data.
29+
> Prison Reference Data Types: PHONE_TYPE, ALERT_TYPE, ETHNICITY, GENDER
30+
> Probation Reference Data Types: PHONE_TYPE, REGISTER_TYPE, ETHNICITY, GENDER
31+
""",
32+
responses = [
33+
ApiResponse(
34+
responseCode = "200",
35+
description = "Successfully returned prison and probation reference data.",
36+
content = [
37+
Content(
38+
schema = Schema(implementation = Response::class),
39+
examples = [
40+
ExampleObject(
41+
"""{
42+
"data": {
43+
"prisonReferenceData": {
44+
"PHONE_TYPE": [
45+
{
46+
"code": "a",
47+
"description": "desc_a"
48+
},
49+
{
50+
"code": "b",
51+
"description": "desc_b"
52+
},
53+
{
54+
"code": "c",
55+
"description": "desc_c"
56+
}
57+
],
58+
"ALERT_TYPE": [
59+
{
60+
"code": "a",
61+
"description": "desc_a"
62+
},
63+
{
64+
"code": "b",
65+
"description": "desc_b"
66+
},
67+
{
68+
"code": "c",
69+
"description": "desc_c"
70+
}
71+
],
72+
"ETHNICITY": [
73+
{
74+
"code": "a",
75+
"description": "desc_a"
76+
},
77+
{
78+
"code": "b",
79+
"description": "desc_b"
80+
},
81+
{
82+
"code": "c",
83+
"description": "desc_c"
84+
}
85+
],
86+
"GENDER": [
87+
{
88+
"code": "a",
89+
"description": "desc_a"
90+
},
91+
{
92+
"code": "b",
93+
"description": "desc_b"
94+
},
95+
{
96+
"code": "c",
97+
"description": "desc_c"
98+
}
99+
]
100+
},
101+
"probationReferenceData": {
102+
"PHONE_TYPE": [
103+
{
104+
"code": "a",
105+
"description": "desc_a"
106+
},
107+
{
108+
"code": "b",
109+
"description": "desc_b"
110+
},
111+
{
112+
"code": "c",
113+
"description": "desc_c"
114+
}
115+
],
116+
"REGISTER_TYPE": [
117+
{
118+
"code": "a",
119+
"description": "desc_a"
120+
},
121+
{
122+
"code": "b",
123+
"description": "desc_b"
124+
},
125+
{
126+
"code": "c",
127+
"description": "desc_c"
128+
}
129+
],
130+
"ETHNICITY": [
131+
{
132+
"code": "a",
133+
"description": "desc_a"
134+
},
135+
{
136+
"code": "b",
137+
"description": "desc_b"
138+
},
139+
{
140+
"code": "c",
141+
"description": "desc_c"
142+
}
143+
],
144+
"GENDER": [
145+
{
146+
"code": "a",
147+
"description": "desc_a"
148+
},
149+
{
150+
"code": "b",
151+
"description": "desc_b"
152+
},
153+
{
154+
"code": "c",
155+
"description": "desc_c"
156+
}
157+
]
158+
},
159+
"errors": []
160+
}""",
161+
),
162+
],
163+
),
164+
],
165+
),
166+
ApiResponse(responseCode = "500", content = [Content(schema = Schema(ref = "#/components/schemas/InternalServerError"))]),
167+
],
168+
)
169+
fun getReferenceData(): Response<ReferenceData?> {
170+
return referenceDataService.referenceData()
171+
}
172+
}

src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/extensions/WebClientWrapper.kt

+9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import org.springframework.web.reactive.function.BodyInserters
66
import org.springframework.web.reactive.function.client.ExchangeStrategies
77
import org.springframework.web.reactive.function.client.WebClient
88
import org.springframework.web.reactive.function.client.WebClientResponseException
9+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Response
910
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApi
1011
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
1112

@@ -112,3 +113,11 @@ class WebClientWrapper(
112113
)
113114
}
114115
}
116+
117+
inline fun <reified T> WebClientWrapper.WebClientWrapperResponse<T>.getOrError(error: (errors: List<UpstreamApiError>) -> Response<Any?>): T {
118+
if (this is WebClientWrapper.WebClientWrapperResponse.Error) {
119+
error(this.errors)
120+
}
121+
val success = this as WebClientWrapper.WebClientWrapperResponse.Success
122+
return success.data
123+
}

src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/gateways/ProbationOffenderSearchGateway.kt

+3-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import uk.gov.justice.digital.hmpps.hmppsintegrationapi.extensions.WebClientWrap
88
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.extensions.WebClientWrapper.WebClientWrapperResponse
99
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Address
1010
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Person
11+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.PersonOnProbation
1112
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Response
1213
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApi
1314
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
@@ -72,9 +73,9 @@ class ProbationOffenderSearchGateway(
7273
}
7374
}
7475

75-
fun getPerson(id: String? = null): Response<Person?> {
76+
fun getPerson(id: String? = null): Response<PersonOnProbation?> {
7677
val offender = getOffender(id)
77-
return Response(data = offender.data?.toPerson(), errors = offender.errors)
78+
return Response(data = offender.data?.toPersonOnProbation(), errors = offender.errors)
7879
}
7980

8081
fun getPersons(

src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/models/hmpps/OffenderSearchResponse.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ package uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps
22

33
data class OffenderSearchResponse(
44
val prisonerOffenderSearch: Person?,
5-
val probationOffenderSearch: Person?,
5+
val probationOffenderSearch: PersonOnProbation?,
66
)

src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/models/hmpps/Person.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonAlias
44
import io.swagger.v3.oas.annotations.media.Schema
55
import java.time.LocalDate
66

7-
data class Person(
7+
open class Person(
88
@Schema(description = "First name", example = "John")
99
val firstName: String,
1010
@JsonAlias("surname")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps
2+
3+
class PersonOnProbation(
4+
person: Person,
5+
val underActiveSupervision: Boolean,
6+
) : Person(
7+
firstName = person.firstName,
8+
lastName = person.lastName,
9+
middleName = person.middleName,
10+
dateOfBirth = person.dateOfBirth,
11+
gender = person.gender,
12+
ethnicity = person.ethnicity,
13+
aliases = person.aliases,
14+
identifiers = person.identifiers,
15+
pncId = person.pncId,
16+
hmppsId = person.hmppsId,
17+
contactDetails = person.contactDetails,
18+
)

src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/models/probationoffendersearch/Offender.kt

+9-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.probationoffende
22

33
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Identifiers
44
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Person
5+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.PersonOnProbation
56
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.PersonProtectedCharacteristics
67
import java.time.LocalDate
78

@@ -16,8 +17,9 @@ data class Offender(
1617
val contactDetails: ContactDetails? = null,
1718
val otherIds: OtherIds = OtherIds(),
1819
val age: Number = 0,
20+
val activeProbationManagedSentence: Boolean = false,
1921
) {
20-
fun toPerson(): Person =
22+
fun toPerson() =
2123
Person(
2224
firstName = this.firstName,
2325
lastName = this.surname,
@@ -37,6 +39,12 @@ data class Offender(
3739
contactDetails = this.contactDetails?.toContactDetails(),
3840
)
3941

42+
fun toPersonOnProbation() =
43+
PersonOnProbation(
44+
toPerson(),
45+
underActiveSupervision = this.activeProbationManagedSentence,
46+
)
47+
4048
fun toPersonProtectedCharacteristics(): PersonProtectedCharacteristics =
4149
PersonProtectedCharacteristics(
4250
this.age,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
data class ReferenceData(
2+
val prisonReferenceData: Map<String, List<ReferenceDataItem>>?,
3+
val probationReferenceData: Map<String, List<ReferenceDataItem>>?,
4+
)
5+
6+
data class ReferenceDataItem(
7+
val code: String,
8+
val description: String,
9+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.services
2+
3+
import ReferenceData
4+
import ReferenceDataItem
5+
import org.springframework.beans.factory.annotation.Value
6+
import org.springframework.http.HttpMethod
7+
import org.springframework.stereotype.Service
8+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.extensions.WebClientWrapper
9+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.extensions.getOrError
10+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.gateways.HmppsAuthGateway
11+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Response
12+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApi
13+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.nomis.NomisReferenceCode
14+
15+
@Service
16+
class ReferenceDataService(
17+
@Value("\${services.ndelius.base-url}") deliusBaseUrl: String,
18+
@Value("\${services.prison-api.base-url}") prisonBaseUrl: String,
19+
private val hmppsAuthGateway: HmppsAuthGateway,
20+
) {
21+
private val deliusWebClient = WebClientWrapper(deliusBaseUrl)
22+
private val prisonApiWebclient = WebClientWrapper(prisonBaseUrl)
23+
24+
fun referenceData(): Response<ReferenceData?> {
25+
val probationReferenceData =
26+
deliusWebClient.request<ReferenceData>(
27+
HttpMethod.GET,
28+
"/reference-data",
29+
authHeader(),
30+
UpstreamApi.NDELIUS,
31+
).getOrError { (errors) -> return Response(null, errors = listOf(errors)) }.probationReferenceData
32+
33+
val prisonReferenceData =
34+
NomisReferenceDataType.entries.flatMap {
35+
val rd = prisonReferenceData(it.name)
36+
if (rd.errors.isNotEmpty()) {
37+
return Response(data = null, errors = rd.errors)
38+
}
39+
rd.data!!
40+
}.groupByTo(LinkedHashMap(), { NomisReferenceDataType.valueOf(it.domain!!).category }, { ReferenceDataItem(it.code!!, it.description!!) })
41+
42+
return Response(data = ReferenceData(prisonReferenceData, probationReferenceData))
43+
}
44+
45+
private fun prisonReferenceData(domain: String): Response<List<NomisReferenceCode>?> {
46+
val prisonReferenceData =
47+
prisonApiWebclient.requestList<NomisReferenceCode>(
48+
HttpMethod.GET,
49+
"/api/reference-domains/domains/$domain",
50+
prisonAuthHeader(),
51+
UpstreamApi.NOMIS,
52+
).getOrError { (errors) -> return Response(null, errors = listOf(errors)) }
53+
return Response(data = prisonReferenceData)
54+
}
55+
56+
private fun authHeader(): Map<String, String> {
57+
val token = hmppsAuthGateway.getClientToken("nDelius")
58+
return mapOf(
59+
"Authorization" to "Bearer $token",
60+
)
61+
}
62+
63+
private fun prisonAuthHeader(): Map<String, String> {
64+
val token = hmppsAuthGateway.getClientToken("NOMIS")
65+
val version = "1.0"
66+
67+
return mapOf(
68+
"Authorization" to "Bearer $token",
69+
"version" to version,
70+
"Page-Limit" to Int.MAX_VALUE.toString(),
71+
)
72+
}
73+
}
74+
75+
enum class NomisReferenceDataType(val category: String) {
76+
PHONE_USAGE("PHONE_TYPE"),
77+
ALERT("ALERT_TYPE"),
78+
ETHNICITY("ETHNICITY"),
79+
SEX("GENDER"),
80+
}

src/main/resources/application-dev.yml

+3
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ authorisation:
4848
- "/v1/persons/.*/risks/categories"
4949
- "/v1/persons/.*/person-responsible-officer"
5050
- "/v1/persons/.*/risk-management-plan"
51+
- "/v1/hmpps/reference-data"
5152
ctrlo:
5253
- "/v1/epf/person-details/.*/[^/]*$"
5354
moj-pes:
@@ -72,6 +73,7 @@ authorisation:
7273
- "/v1/persons/.*/licences/conditions"
7374
- "/v1/persons/.*/person-responsible-officer"
7475
- "/v1/persons/.*/status-information"
76+
- "/v1/hmpps/reference-data"
7577
event-service:
7678
- "/v1/config/authorisation"
7779
mryall:
@@ -87,3 +89,4 @@ authorisation:
8789
- "/v1/persons/.*/licences/conditions"
8890
- "/v1/persons/.*/person-responsible-officer"
8991
- "/v1/persons/.*/status-information"
92+
- "/v1/hmpps/reference-data"

0 commit comments

Comments
 (0)