diff --git a/projects/manage-supervision-and-delius/build.gradle.kts b/projects/manage-supervision-and-delius/build.gradle.kts index 16e4a88d4..14d3844de 100644 --- a/projects/manage-supervision-and-delius/build.gradle.kts +++ b/projects/manage-supervision-and-delius/build.gradle.kts @@ -5,9 +5,10 @@ apply(plugin = "com.google.cloud.tools.jib") dependencies { implementation(project(":libs:audit")) implementation(project(":libs:commons")) - implementation(project(":libs:oauth-server")) - implementation(project(":libs:limited-access")) implementation(project(":libs:document-management")) + implementation(project(":libs:limited-access")) + implementation(project(":libs:oauth-client")) + implementation(project(":libs:oauth-server")) implementation("org.springframework.boot:spring-boot-starter-actuator") implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-security") diff --git a/projects/manage-supervision-and-delius/deploy/values-dev.yml b/projects/manage-supervision-and-delius/deploy/values-dev.yml index 0e067f701..c095d7592 100644 --- a/projects/manage-supervision-and-delius/deploy/values-dev.yml +++ b/projects/manage-supervision-and-delius/deploy/values-dev.yml @@ -11,6 +11,7 @@ generic-service: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: https://sign-in-dev.hmpps.service.justice.gov.uk/auth/.well-known/jwks.json SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: https://sign-in-dev.hmpps.service.justice.gov.uk/auth/issuer INTEGRATIONS_ALFRESCO_URL: https://hmpps-delius-alfresco-test.apps.live.cloud-platform.service.justice.gov.uk/alfresco/service/noms-spg + INTEGRATIONS_PROBATION-SEARCH_URL: https://probation-offender-search-dev.hmpps.service.justice.gov.uk generic-prometheus-alerts: businessHoursOnly: true diff --git a/projects/manage-supervision-and-delius/deploy/values-preprod.yml b/projects/manage-supervision-and-delius/deploy/values-preprod.yml index 3254261ab..b0fe4ff00 100644 --- a/projects/manage-supervision-and-delius/deploy/values-preprod.yml +++ b/projects/manage-supervision-and-delius/deploy/values-preprod.yml @@ -10,6 +10,7 @@ generic-service: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: https://sign-in-preprod.hmpps.service.justice.gov.uk/auth/.well-known/jwks.json SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: https://sign-in-preprod.hmpps.service.justice.gov.uk/auth/issuer INTEGRATIONS_ALFRESCO_URL: https://alfresco.pre-prod.delius.probation.hmpps.dsd.io/alfresco/service/noms-spg + INTEGRATIONS_PROBATION-SEARCH_URL: https://probation-offender-search-preprod.hmpps.service.justice.gov.uk generic-prometheus-alerts: businessHoursOnly: true \ No newline at end of file diff --git a/projects/manage-supervision-and-delius/deploy/values-prod.yml b/projects/manage-supervision-and-delius/deploy/values-prod.yml index 21aa2fe53..91cc00dd1 100644 --- a/projects/manage-supervision-and-delius/deploy/values-prod.yml +++ b/projects/manage-supervision-and-delius/deploy/values-prod.yml @@ -7,3 +7,4 @@ generic-service: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: https://sign-in.hmpps.service.justice.gov.uk/auth/.well-known/jwks.json SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: https://sign-in.hmpps.service.justice.gov.uk/auth/issuer INTEGRATIONS_ALFRESCO_URL: https://alfresco.probation.service.justice.gov.uk/alfresco/service/noms-spg + INTEGRATIONS_PROBATION-SEARCH_URL: https://probation-offender-search.hmpps.service.justice.gov.uk \ No newline at end of file diff --git a/projects/manage-supervision-and-delius/deploy/values.yaml b/projects/manage-supervision-and-delius/deploy/values.yaml index e44cd701d..925d600ff 100644 --- a/projects/manage-supervision-and-delius/deploy/values.yaml +++ b/projects/manage-supervision-and-delius/deploy/values.yaml @@ -20,6 +20,9 @@ generic-service: SPRING_DATASOURCE_PASSWORD: DB_PASSWORD manage-supervision-and-delius-sentry: SENTRY_DSN: SENTRY_DSN + manage-supervision-and-delius-client-credentials: + OAUTH2_CLIENT-ID: CLIENT_ID + OAUTH2_CLIENT-SECRET: CLIENT_SECRET generic-prometheus-alerts: targetApplication: manage-supervision-and-delius diff --git a/projects/manage-supervision-and-delius/src/dev/resources/simulations/mappings/hmpps-auth.json b/projects/manage-supervision-and-delius/src/dev/resources/simulations/mappings/hmpps-auth.json new file mode 100644 index 000000000..e64fc3391 --- /dev/null +++ b/projects/manage-supervision-and-delius/src/dev/resources/simulations/mappings/hmpps-auth.json @@ -0,0 +1,21 @@ +{ + "request": { + "method": "POST", + "urlPath": "/auth/oauth/token" + }, + "response": { + "headers": { + "Content-Type": "application/json" + }, + "status": 200, + "jsonBody": { + "access_token": "token", + "token_type": "bearer", + "expires_in": 9999999999, + "scope": "read write", + "sub": "probation-integration-dev", + "auth_source": "none", + "iss": "https://sign-in-dev.hmpps.service.justice.gov.uk/auth/issuer" + } + } +} \ No newline at end of file diff --git a/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ActivityIntegrationTest.kt b/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ActivityIntegrationTest.kt index a2e030ced..0e17e700d 100644 --- a/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ActivityIntegrationTest.kt +++ b/projects/manage-supervision-and-delius/src/integrationTest/kotlin/uk/gov/justice/digital/hmpps/ActivityIntegrationTest.kt @@ -1,5 +1,9 @@ package uk.gov.justice.digital.hmpps +import com.github.tomakehurst.wiremock.WireMockServer +import com.github.tomakehurst.wiremock.client.WireMock +import com.github.tomakehurst.wiremock.client.WireMock.aResponse +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.equalTo import org.junit.jupiter.api.Test @@ -9,12 +13,18 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT import org.springframework.test.web.servlet.MockMvc import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status import uk.gov.justice.digital.hmpps.api.model.activity.PersonActivity +import uk.gov.justice.digital.hmpps.client.ActivitySearchRequest +import uk.gov.justice.digital.hmpps.client.ContactSearchResponse +import uk.gov.justice.digital.hmpps.client.ContactSearchResult import uk.gov.justice.digital.hmpps.data.generator.ContactGenerator import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator.OVERVIEW import uk.gov.justice.digital.hmpps.service.toActivity import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.contentAsJson +import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.objectMapper +import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withJson import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withToken @AutoConfigureMockMvc @@ -23,6 +33,61 @@ internal class ActivityIntegrationTest { @Autowired lateinit var mockMvc: MockMvc + @Autowired + lateinit var wireMockServer: WireMockServer + + @Test + fun `activity search calls probation offender activity search`() { + + val person = OVERVIEW + val searchResponse = ContactSearchResponse( + page = 0, totalResults = 10, totalPages = 11, size = 3, + results = listOf( + ContactSearchResult( + crn = person.crn, + id = ContactGenerator.FIRST_APPT_CONTACT.id + ), + ContactSearchResult( + crn = person.crn, + id = ContactGenerator.FIRST_NON_APPT_CONTACT.id + ), + ContactSearchResult( + crn = person.crn, + id = ContactGenerator.NEXT_APPT_CONTACT.id + ) + ) + ) + wireMockServer.stubFor( + WireMock.post(urlPathEqualTo("/probation-search/search/activity")) + .willReturn( + aResponse() + .withStatus(200) + .withHeader("Content-Type", "application/json") + .withBody(objectMapper.writeValueAsString(searchResponse)) + ) + ) + + val res = mockMvc + .perform( + post("/activity/${person.crn}").withToken() + .withJson( + ActivitySearchRequest(crn = person.crn) + ) + ) + .andExpect(status().isOk) + .andReturn().response.contentAsJson() + + assertThat(res.personSummary.crn, equalTo(person.crn)) + assertThat(res.activities.size, equalTo(3)) + assertThat(res.activities[0].id, equalTo(ContactGenerator.FIRST_APPT_CONTACT.id)) + assertThat( + res.activities[0].location?.officeName, + equalTo(ContactGenerator.FIRST_APPT_CONTACT.toActivity().location?.officeName) + ) + assertThat(res.activities[1].id, equalTo(ContactGenerator.FIRST_NON_APPT_CONTACT.id)) + assertThat(res.activities[2].id, equalTo(ContactGenerator.NEXT_APPT_CONTACT.id)) + } + @Test fun `all person activity is returned`() { diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/ActivityController.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/ActivityController.kt index 745cc8239..e68e7dfee 100644 --- a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/ActivityController.kt +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/controller/ActivityController.kt @@ -2,11 +2,10 @@ package uk.gov.justice.digital.hmpps.api.controller import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.tags.Tag +import org.springframework.data.domain.PageRequest import org.springframework.security.access.prepost.PreAuthorize -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController +import org.springframework.web.bind.annotation.* +import uk.gov.justice.digital.hmpps.api.model.activity.PersonActivitySearchRequest import uk.gov.justice.digital.hmpps.service.ActivityService @RestController @@ -18,4 +17,13 @@ class ActivityController(private val activityService: ActivityService) { @GetMapping @Operation(summary = "Gets all activity for a person’ ") fun getPersonActivity(@PathVariable crn: String) = activityService.getPersonActivity(crn) -} \ No newline at end of file + + @PostMapping + @Operation(summary = "Activity Log Search’ ") + fun activitySearch( + @PathVariable crn: String, + @RequestBody searchRequest: PersonActivitySearchRequest, + @RequestParam(required = false, defaultValue = "0") page: Int, + @RequestParam(required = false, defaultValue = "10") size: Int + ) = activityService.activitySearch(crn, searchRequest, PageRequest.of(page, size)) +} diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/activity/PersonActivity.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/activity/PersonActivity.kt index 403534531..6fec61a2a 100644 --- a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/activity/PersonActivity.kt +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/api/model/activity/PersonActivity.kt @@ -1,8 +1,28 @@ package uk.gov.justice.digital.hmpps.api.model.activity +import com.fasterxml.jackson.annotation.JsonFormat import uk.gov.justice.digital.hmpps.api.model.PersonSummary +import java.time.LocalDate data class PersonActivity( val personSummary: PersonSummary, val activities: List -) \ No newline at end of file +) + +data class PersonActivitySearchResponse( + val size: Int, + val page: Int, + val totalResults: Long, + val totalPages: Int, + val personSummary: PersonSummary, + val activities: List +) + +data class PersonActivitySearchRequest( + val keywords: String? = "", + @JsonFormat(pattern = "yyyy-MM-dd") + val dateFrom: LocalDate? = null, + @JsonFormat(pattern = "yyyy-MM-dd") + val dateTo: LocalDate? = null, + val filters: List = emptyList(), +) diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/client/ProbationSearchClient.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/client/ProbationSearchClient.kt new file mode 100644 index 000000000..be26ca20b --- /dev/null +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/client/ProbationSearchClient.kt @@ -0,0 +1,40 @@ +package uk.gov.justice.digital.hmpps.client + +import com.fasterxml.jackson.annotation.JsonFormat +import org.springframework.web.bind.annotation.RequestBody +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.service.annotation.PostExchange +import java.time.LocalDate + +interface ProbationSearchClient { + @PostExchange(url = "/search/activity") + fun contactSearch( + @RequestBody body: ActivitySearchRequest, + @RequestParam page: Int = 0, + @RequestParam size: Int = 10): ContactSearchResponse +} + +data class ActivitySearchRequest( + val crn: String, + val keywords: String? = "", + @JsonFormat(pattern = "yyyy-MM-dd") + val dateFrom: LocalDate? = null, + @JsonFormat(pattern = "yyyy-MM-dd") + val dateTo: LocalDate? = null, + val filters: List = emptyList(), +) + + +data class ContactSearchResponse( + val size: Int, + val page: Int, + val totalResults: Long, + val totalPages: Int, + val results: List, +) + +data class ContactSearchResult( + val crn: String, + val id: Long, + val highlights: Map> = mapOf() +) diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/config/RestClientConfig.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/config/RestClientConfig.kt new file mode 100644 index 000000000..a53b9dc10 --- /dev/null +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/config/RestClientConfig.kt @@ -0,0 +1,16 @@ +package uk.gov.justice.digital.hmpps.config + +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.web.client.RestClient +import uk.gov.justice.digital.hmpps.client.ProbationSearchClient +import uk.gov.justice.digital.hmpps.config.security.createClient + +@Configuration +class RestClientConfig(private val oauth2Client: RestClient) { + + @Bean + fun probationSearchClient(@Value("\${integrations.probation-search.url}") apiBaseUrl: String): ProbationSearchClient = + createClient(oauth2Client.mutate().baseUrl(apiBaseUrl).build()) +} diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/overview/entity/Contact.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/overview/entity/Contact.kt index bc38af310..543cfcd90 100644 --- a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/overview/entity/Contact.kt +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/integrations/delius/overview/entity/Contact.kt @@ -255,6 +255,8 @@ interface ContactRepository : JpaRepository { ) fun findByPersonId(personId: Long): List + fun findByPersonIdAndIdIn(personId: Long, ids: List): List + fun findByPersonIdAndEventIdIn(personId: Long, eventId: List): List fun findByPersonIdAndId(personId: Long, id: Long): Contact? diff --git a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ActivityService.kt b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ActivityService.kt index f41006976..66c5622cb 100644 --- a/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ActivityService.kt +++ b/projects/manage-supervision-and-delius/src/main/kotlin/uk/gov/justice/digital/hmpps/service/ActivityService.kt @@ -1,9 +1,14 @@ package uk.gov.justice.digital.hmpps.service +import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import uk.gov.justice.digital.hmpps.api.model.activity.Activity import uk.gov.justice.digital.hmpps.api.model.activity.PersonActivity +import uk.gov.justice.digital.hmpps.api.model.activity.PersonActivitySearchRequest +import uk.gov.justice.digital.hmpps.api.model.activity.PersonActivitySearchResponse +import uk.gov.justice.digital.hmpps.client.ActivitySearchRequest +import uk.gov.justice.digital.hmpps.client.ProbationSearchClient import uk.gov.justice.digital.hmpps.integrations.delius.overview.entity.ContactRepository import uk.gov.justice.digital.hmpps.integrations.delius.overview.entity.PersonRepository import uk.gov.justice.digital.hmpps.integrations.delius.overview.entity.getSummary @@ -11,9 +16,41 @@ import uk.gov.justice.digital.hmpps.integrations.delius.overview.entity.getSumma @Service class ActivityService( private val personRepository: PersonRepository, - private val contactRepository: ContactRepository + private val contactRepository: ContactRepository, + private val probationSearchClient: ProbationSearchClient ) { + @Transactional + fun activitySearch( + crn: String, + searchRequest: PersonActivitySearchRequest, + pageable: Pageable + ): PersonActivitySearchResponse { + val summary = personRepository.getSummary(crn) + val probationSearchRequest = ActivitySearchRequest( + crn = crn, + keywords = searchRequest.keywords, + dateFrom = searchRequest.dateFrom, + dateTo = searchRequest.dateTo, + filters = searchRequest.filters + ) + val response = + probationSearchClient.contactSearch(probationSearchRequest, pageable.pageNumber, pageable.pageSize) + val ids = response.results.map { it.id } + val contacts = ids.map { contactId -> + contactRepository.findByPersonIdAndIdIn(summary.id, ids).associateBy { it.id }[contactId]!! + } + + return PersonActivitySearchResponse( + size = response.size, + page = response.page, + totalResults = response.totalResults, + totalPages = response.totalPages, + personSummary = summary.toPersonSummary(), + activities = contacts.map { it.toActivity() } + ) + } + @Transactional fun getPersonActivity(crn: String): PersonActivity { val summary = personRepository.getSummary(crn) diff --git a/projects/manage-supervision-and-delius/src/main/resources/application.yml b/projects/manage-supervision-and-delius/src/main/resources/application.yml index f2615f2e4..e195b3c1d 100644 --- a/projects/manage-supervision-and-delius/src/main/resources/application.yml +++ b/projects/manage-supervision-and-delius/src/main/resources/application.yml @@ -17,6 +17,16 @@ spring: query.mutation_strategy.global_temporary: create_tables: false drop_tables: false + security.oauth2.client: + registration: + default: + provider: hmpps-auth + authorization-grant-type: client_credentials + client-id: ${oauth2.client-id} + client-secret: ${oauth2.client-secret} + provider: + hmpps-auth: + token-uri: http://localhost:${wiremock.port}/auth/oauth/token threads.virtual.enabled: true ldap: base: ou=Users,dc=moj,dc=com @@ -54,6 +64,13 @@ seed.database: true wiremock.enabled: true +integrations: + probation-search.url: http://localhost:${wiremock.port}/probation-search + +oauth2: + client-id: manage-supervision-and-delius + client-secret: manage-supervision-and-delius + logging.level: uk.gov.justice.digital.hmpps: DEBUG org.hibernate.tool.schema: ERROR