Skip to content

Commit aaed0ae

Browse files
HMAI-270 - Get contacts controller + new PaginatedResponse changes (#703)
* Get prisoner contacts controller and tests * Contacts model updates and int tests for get prisoner contacts * New factory methods on PaginatedResponse * Fix build errors * Use interface extension method * Fix unit tests * Add pagination examples --------- Co-authored-by: wcdkj <will.clark@digital.justice.gov.uk>
1 parent 3dae43e commit aaed0ae

File tree

13 files changed

+462
-47
lines changed

13 files changed

+462
-47
lines changed

src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/controllers/v1/person/PersonController.kt

+36
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import jakarta.validation.ValidationException
1010
import org.springframework.beans.factory.annotation.Autowired
1111
import org.springframework.web.bind.annotation.GetMapping
1212
import org.springframework.web.bind.annotation.PathVariable
13+
import org.springframework.web.bind.annotation.RequestAttribute
1314
import org.springframework.web.bind.annotation.RequestMapping
1415
import org.springframework.web.bind.annotation.RequestParam
1516
import org.springframework.web.bind.annotation.RestController
@@ -20,13 +21,17 @@ import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.ImageMetada
2021
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.OffenderSearchResponse
2122
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.Person
2223
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.PersonName
24+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.PrisonerContact
2325
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApi
2426
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError
2527
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.UpstreamApiError.Type.ENTITY_NOT_FOUND
28+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.interfaces.toPaginatedResponse
29+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.roleconfig.ConsumerFilters
2630
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.GetImageMetadataForPersonService
2731
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.GetNameForPersonService
2832
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.GetPersonService
2933
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.GetPersonsService
34+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.GetPrisonerContactsService
3035
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.services.internal.AuditService
3136
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.util.PaginatedResponse
3237
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.util.paginateWith
@@ -41,6 +46,7 @@ class PersonController(
4146
@Autowired val getPersonsService: GetPersonsService,
4247
@Autowired val getNameForPersonService: GetNameForPersonService,
4348
@Autowired val getImageMetadataForPersonService: GetImageMetadataForPersonService,
49+
@Autowired val getPrisonerContactsService: GetPrisonerContactsService,
4450
@Autowired val auditService: AuditService,
4551
) {
4652
@GetMapping
@@ -157,6 +163,36 @@ class PersonController(
157163
return DataResponse(response.data)
158164
}
159165

166+
@GetMapping("{hmppsId}/contacts")
167+
@Operation(
168+
summary = "Returns a prisoners contacts.",
169+
responses = [
170+
ApiResponse(responseCode = "200", useReturnTypeSchema = true, description = "Successfully found a prisoners contacts."),
171+
ApiResponse(responseCode = "404", content = [Content(schema = Schema(ref = "#/components/schemas/PersonNotFound"))]),
172+
ApiResponse(responseCode = "500", content = [Content(schema = Schema(ref = "#/components/schemas/InternalServerError"))]),
173+
],
174+
)
175+
fun getPrisonersContacts(
176+
@Parameter(description = "The HMPPS ID of the prisoner") @PathVariable hmppsId: String,
177+
@Parameter(description = "The page number (starting from 1)", schema = Schema(minimum = "1")) @RequestParam(required = false, defaultValue = "1", name = "page") page: Int,
178+
@Parameter(description = "The maximum number of results for a page", schema = Schema(minimum = "1")) @RequestParam(required = false, defaultValue = "10", name = "perPage") perPage: Int,
179+
@RequestAttribute filters: ConsumerFilters?,
180+
): PaginatedResponse<PrisonerContact> {
181+
val response = getPrisonerContactsService.execute(hmppsId, page, perPage, filters)
182+
183+
if (response.hasError(UpstreamApiError.Type.ENTITY_NOT_FOUND)) {
184+
throw EntityNotFoundException("Could not find person with id: $hmppsId")
185+
}
186+
187+
if (response.hasError(UpstreamApiError.Type.BAD_REQUEST)) {
188+
throw ValidationException("Invalid HMPPS ID: $hmppsId")
189+
}
190+
191+
auditService.createEvent("GET_PRISONER_CONTACTS", mapOf("hmppsId" to hmppsId))
192+
193+
return response.data.toPaginatedResponse()
194+
}
195+
160196
private fun isValidISODateFormat(dateString: String): Boolean =
161197
try {
162198
LocalDate.parse(dateString, DateTimeFormatter.ISO_DATE)
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps
22

3+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.interfaces.IPaginatedObject
4+
35
data class PaginatedPrisonerContacts(
4-
var contacts: List<PrisonerContact>,
5-
val isLast: Boolean,
6-
val numberOfElements: Int,
7-
val number: Int,
8-
val size: Int,
9-
val totalElements: Long,
10-
val totalPages: Int,
11-
)
6+
override val content: List<PrisonerContact>,
7+
override val isLastPage: Boolean,
8+
override val count: Int,
9+
override val page: Int,
10+
override val perPage: Int,
11+
override val totalCount: Long,
12+
override val totalPages: Int,
13+
) : IPaginatedObject<PrisonerContact>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty
4+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.personalRelationships.interfaces.IRelationship
5+
6+
data class Relationship(
7+
@JsonProperty("relationshipTypeCode")
8+
override val relationshipTypeCode: String?,
9+
@JsonProperty("relationshipTypeDescription")
10+
override val relationshipTypeDescription: String?,
11+
@JsonProperty("relationshipToPrisonerCode")
12+
override val relationshipToPrisonerCode: String?,
13+
@JsonProperty("relationshipToPrisonerDescription")
14+
override val relationshipToPrisonerDescription: String?,
15+
@JsonProperty("approvedVisitor")
16+
val approvedPrisoner: Boolean?,
17+
@JsonProperty("nextOfKin")
18+
val nextOfKin: Boolean?,
19+
@JsonProperty("emergencyContact")
20+
val emergencyContact: Boolean?,
21+
@JsonProperty("isRelationshipActive")
22+
val isRelationshipActive: Boolean?,
23+
@JsonProperty("currentTerm")
24+
val currentTerm: Boolean?,
25+
@JsonProperty("comments")
26+
val comments: String?,
27+
) : IRelationship
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.interfaces
2+
3+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.util.PaginatedResponse
4+
5+
interface IPaginatedObject<T> {
6+
val content: List<T>
7+
val isLastPage: Boolean
8+
val count: Int
9+
val page: Int
10+
val perPage: Int
11+
val totalCount: Long
12+
val totalPages: Int
13+
}
14+
15+
fun <T> IPaginatedObject<T>?.toPaginatedResponse(): PaginatedResponse<T> = PaginatedResponse.fromPaginatedObject(this)

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

+6-6
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ data class PRPaginatedPrisonerContacts(
1919
) {
2020
fun toPaginatedPrisonerContacts(): PaginatedPrisonerContacts =
2121
PaginatedPrisonerContacts(
22-
contacts = this.contacts.map { it.toPrisonerContact() },
23-
isLast = this.last,
24-
numberOfElements = this.numberOfElements.toInt(),
25-
number = this.number.toInt(),
26-
size = this.size.toInt(),
27-
totalElements = this.totalElements,
22+
content = this.contacts.map { it.toPrisonerContact() },
23+
isLastPage = this.last,
24+
count = this.numberOfElements.toInt(),
25+
page = this.number.toInt(),
26+
perPage = this.size.toInt(),
27+
totalCount = this.totalElements,
2828
totalPages = this.totalPages.toInt(),
2929
)
3030
}

src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/prismMocks/x4022-personal-relationships.json

+16-8
Original file line numberDiff line numberDiff line change
@@ -12803,7 +12803,8 @@
1280312803
"page": {
1280412804
"type": "integer",
1280512805
"format": "int32",
12806-
"minimum": 0
12806+
"minimum": 0,
12807+
"example": 1
1280712808
},
1280812809
"size": {
1280912810
"type": "integer",
@@ -12830,7 +12831,8 @@
1283012831
},
1283112832
"pageSize": {
1283212833
"type": "integer",
12833-
"format": "int32"
12834+
"format": "int32",
12835+
"example": 10
1283412836
},
1283512837
"unpaged": {
1283612838
"type": "boolean"
@@ -12840,7 +12842,8 @@
1284012842
},
1284112843
"pageNumber": {
1284212844
"type": "integer",
12843-
"format": "int32"
12845+
"format": "int32",
12846+
"example": 1
1284412847
}
1284512848
}
1284612849
},
@@ -13061,29 +13064,34 @@
1306113064
},
1306213065
"totalElements": {
1306313066
"type": "integer",
13064-
"format": "int64"
13067+
"format": "int64",
13068+
"example": 1
1306513069
},
1306613070
"totalPages": {
1306713071
"type": "integer",
13068-
"format": "int32"
13072+
"format": "int32",
13073+
"example": 1
1306913074
},
1307013075
"first": {
1307113076
"type": "boolean"
1307213077
},
1307313078
"size": {
1307413079
"type": "integer",
13075-
"format": "int32"
13080+
"format": "int32",
13081+
"example": 10
1307613082
},
1307713083
"number": {
1307813084
"type": "integer",
13079-
"format": "int32"
13085+
"format": "int32",
13086+
"example": 1
1308013087
},
1308113088
"sort": {
1308213089
"$ref": "#/components/schemas/SortObject"
1308313090
},
1308413091
"numberOfElements": {
1308513092
"type": "integer",
13086-
"format": "int32"
13093+
"format": "int32",
13094+
"example": 1
1308713095
},
1308813096
"empty": {
1308913097
"type": "boolean"

src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/util/Paginate.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ fun <T> List<T>.paginateWith(
1212
val end = (start + paginationOptions.pageSize).coerceAtMost(this.size)
1313

1414
if (start > end) {
15-
return PaginatedResponse(PageImpl(listOf<T>(), paginationOptions, this.size.toLong()))
15+
return PaginatedResponse.fromPageableResponse(PageImpl(listOf<T>(), paginationOptions, this.size.toLong()))
1616
}
1717

18-
return PaginatedResponse(PageImpl(this.subList(start, end), paginationOptions, this.count().toLong()))
18+
return PaginatedResponse.fromPageableResponse(PageImpl(this.subList(start, end), paginationOptions, this.count().toLong()))
1919
}

src/main/kotlin/uk/gov/justice/digital/hmpps/hmppsintegrationapi/util/PaginatedResponse.kt

+58-23
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,67 @@ package uk.gov.justice.digital.hmpps.hmppsintegrationapi.util
22

33
import io.swagger.v3.oas.annotations.media.Schema
44
import org.springframework.data.domain.Page
5+
import uk.gov.justice.digital.hmpps.hmppsintegrationapi.models.hmpps.interfaces.IPaginatedObject
56

67
class PaginatedResponse<T>(
7-
@Schema(hidden = true) pageableResponse: Page<T>,
8+
val data: List<T>,
9+
val pagination: Pagination,
810
) {
9-
val data: List<T> = pageableResponse.content
10-
val pagination: Pagination = Pagination(pageableResponse)
11+
companion object {
12+
fun <T> fromPageableResponse(pageableResponse: Page<T>): PaginatedResponse<T> {
13+
val data: List<T> = pageableResponse.content
14+
val pagination =
15+
Pagination(
16+
isLastPage = pageableResponse.isLast,
17+
count = pageableResponse.numberOfElements,
18+
page = pageableResponse.number + 1,
19+
perPage = pageableResponse.size,
20+
totalCount = pageableResponse.totalElements,
21+
totalPages = pageableResponse.totalPages,
22+
)
23+
return PaginatedResponse(data, pagination)
24+
}
1125

12-
inner class Pagination(
13-
pageableResponse: Page<T>,
14-
) {
15-
@Schema(description = "Is the current page the last one?", example = "true")
16-
val isLastPage: Boolean = pageableResponse.isLast
17-
18-
@Schema(description = "The number of results in `data` for the current page", example = "1")
19-
val count: Int = pageableResponse.numberOfElements
20-
21-
@Schema(description = "The current page number", example = "1")
22-
val page: Int = pageableResponse.number + 1
23-
24-
@Schema(description = "The maximum number of results in `data` for a page", example = "10")
25-
val perPage: Int = pageableResponse.size
26-
27-
@Schema(description = "The total number of results in `data` across all pages", example = "1")
28-
val totalCount: Long = pageableResponse.totalElements
29-
30-
@Schema(description = "The total number of pages", example = "1")
31-
val totalPages: Int = pageableResponse.totalPages
26+
fun <T> fromPaginatedObject(paginatedObject: IPaginatedObject<T>?): PaginatedResponse<T> {
27+
if (paginatedObject == null) {
28+
return PaginatedResponse(
29+
emptyList(),
30+
Pagination(
31+
isLastPage = true,
32+
count = 0,
33+
page = 0,
34+
perPage = 0,
35+
totalCount = 0,
36+
totalPages = 0,
37+
),
38+
)
39+
}
40+
return PaginatedResponse(
41+
data = paginatedObject.content,
42+
Pagination(
43+
isLastPage = paginatedObject.isLastPage,
44+
count = paginatedObject.count,
45+
page = paginatedObject.page,
46+
perPage = paginatedObject.perPage,
47+
totalCount = paginatedObject.totalCount,
48+
totalPages = paginatedObject.totalPages,
49+
),
50+
)
51+
}
3252
}
3353
}
54+
55+
data class Pagination(
56+
@Schema(description = "Is the current page the last one?", example = "true")
57+
val isLastPage: Boolean,
58+
@Schema(description = "The number of results in `data` for the current page", example = "1")
59+
val count: Int,
60+
@Schema(description = "The current page number", example = "1")
61+
val page: Int,
62+
@Schema(description = "The maximum number of results in `data` for a page", example = "10")
63+
val perPage: Int,
64+
@Schema(description = "The total number of results in `data` across all pages", example = "1")
65+
val totalCount: Long,
66+
@Schema(description = "The total number of pages", example = "1")
67+
val totalPages: Int,
68+
)

src/main/resources/application-integration-test.yml

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ authorisation:
106106
- "/v1/visit/[^/]*$"
107107
- "/v1/prison/.*/visit/search[^/]*$"
108108
- "/v1/contacts/[^/]*$"
109+
- "/v1/persons/.*/contacts[^/]*$"
109110
filters:
110111
config-test:
111112
include:

src/main/resources/application-test.yml

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ authorisation:
107107
- "/v1/visit/[^/]*$"
108108
- "/v1/prison/.*/visit/search[^/]*$"
109109
- "/v1/contacts/[^/]*$"
110+
- "/v1/persons/.*/contacts[^/]*$"
110111
config-test:
111112
include:
112113
- "/v1/config/authorisation"

0 commit comments

Comments
 (0)