From 24c6c38e5a7cac5174d54689a0a3f5d4c82f96df Mon Sep 17 00:00:00 2001 From: AlexYoungmanMoJ Date: Fri, 24 Jan 2025 14:42:42 +0000 Subject: [PATCH] Readme and OpenAPI update (#48) * Updated Readme * Update openapi spec for plr * Added response codes * Added scenarios for /learner and UAT link --- Dockerfile | 2 + README.md | 239 ++++++++++++------ .../learnerrecordsapi/openapi/FindByULNApi.kt | 127 +++++++++- .../learnerrecordsapi/resource/PLRResource.kt | 2 + 4 files changed, 282 insertions(+), 88 deletions(-) diff --git a/Dockerfile b/Dockerfile index e8b2e2c..04e0fe3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,6 +28,8 @@ COPY --from=builder --chown=appuser:appgroup /app/build/libs/hmpps-learner-recor COPY --from=builder --chown=appuser:appgroup /app/build/libs/applicationinsights-agent*.jar /app/agent.jar COPY --from=builder --chown=appuser:appgroup /app/applicationinsights.json /app COPY --from=builder --chown=appuser:appgroup /app/applicationinsights.dev.json /app +#COPY WebServiceClientCert.pfx /app/WebServiceClientCert.pfx +#RUN ls -la /app/WebServiceClientCert.pfx USER 2000 diff --git a/README.md b/README.md index 1f194b4..a0eb9cf 100644 --- a/README.md +++ b/README.md @@ -5,35 +5,38 @@ for Education (DfE). This repository has been generated from https://github.com/ministryofjustice/hmpps-template-kotlin. -## Endpoints +--- + +## Hosting -This service runs on: -* Local: `http://localhost:8080` +This service is available at: +* Local: `http://localhost:8080/` * Dev: `https://learner-records-api-dev.hmpps.service.justice.gov.uk/` +* UAT - `https://learner-records-api-uat.hmpps.service.justice.gov.uk/` +* Preprod: tbc +* Prod: tbc -### `POST:/learners` -This endpoint is to search for learners by their demographic information. -The search may yield varied results, such as an exact match or possible matches. -The response contains a ULN for each learner found, which may be used on another endpoint to retrieve their respective PLNs. +--- -#### How it works: +## Endpoints -The controller `LearnersResource` accepts a request with a `json` body taking the form of our model `FindLearnerByDemographicRequest`. +The service provides 2 endpoints to consumers. +* `/learners` - Search for a learner's ULN via their demographic data +* `/plr` - Request a learner's learning record via their ULN -Example `json` for use with wiremock (local) +### `POST:/learners` +This endpoint is to search for learners by their demographic information. +The search may yield varied results depending on the accuracy of the demographic information and the DfE data available: -```json -{ - "givenName": "Test_Possible_Match_Two_Learners", - "familyName": "some_name", - "dateOfBirth": "2022-02-02", - "gender": "1", - "lastKnownPostCode": "1234" -} -``` +* No match +* Too many matches +* Possible matches +* Exact match +* Linked Learner Found -Example `json` for use with Dev API +Assuming a successful search, the response should contain a ULN for each learner found. This ULN may be used on the`/plr` endpoint to retrieve their respective PLRs. +Example request body: ```json { "givenName": "Darcie", @@ -44,23 +47,58 @@ Example `json` for use with Dev API } ``` -The model asserts correct inputs for the request body using validation annotations and correct datatypes. In the event that inputs are malformed, error handlers will catch this and return a `400 Bad Request`. - -In the case that the request is accepted, the controller will then call a service object `LRSService` to handle interfacing the LRS API. - -The service `LRSService` has a method called `findLearner` which accepts a single argument of type `FindLearnerByDemographicsRequest`. - -The service has an instance of `retrofit` which provides a way to interface with the LRS API. - -`retrofit` along with `JAXBConverter`, the models under the `models.lrsapi` package, and the interface `LRSApiServiceInterface` handles all the heavy lifting when it comes to parsing `XML` responses from the LRS API. - -When `findLearner` is called, retrofit is used to make a call to the LRS API. The service returns the response as a model `FindLearnerResponse`. +Example response body: +```json +{ + "searchParameters": { + "givenName": "Darcie", + "familyName": "Tucker", + "dateOfBirth": "1976-08-16", + "gender": 2, + "lastKnownPostcode": "CV49EE" + }, + "responseType": "Exact Match", + "matchedLearners": [ + { + "createdDate": "2012-05-25", + "lastUpdatedDate": "2012-05-25", + "uln": "1026893096", + "versionNumber": "1", + "title": "Mrs", + "givenName": "Darcie", + "middleOtherName": "Isla", + "familyName": "Tucker", + "preferredGivenName": "Darcie", + "previousFamilyName": "CAMPBELL", + "familyNameAtAge16": "TUCKER", + "schoolAtAge16": "Mill Hill School Foundation ", + "lastKnownAddressLine1": "1 JOBS LANE", + "lastKnownAddressTown": "COVENTRY", + "lastKnownAddressCountyOrCity": "WEST MIDLANDS", + "lastKnownPostCode": "CV4 9EE", + "dateOfAddressCapture": "2009-04-25", + "dateOfBirth": "1976-08-16", + "placeOfBirth": "Blean ", + "gender": "2", + "emailAddress": "darcie.tucker@aol.compatibilitytest.com", + "scottishCandidateNumber": "845759406", + "abilityToShare": "1", + "learnerStatus": "1", + "verificationType": "1", + "tierLevel": "0" + } + ] +} +``` -The controller parses this into `json` and responds with that to the user. +Response codes: +* 200 - Success +* 400 - Bad Request, malformed inputs +* 401 - Unauthorised +* 403 - Forbidden ### `POST:/plr` - -The `/plr` endpoint is used to request a Learner's learning events by their Unique Learner Number (ULN). +This endpoint is used to request a learner's learning events (or Personal Learning Record [PLR]) by their Unique Learner Number (ULN). Generally when using a valid ULN there should be no issues with this request, but there are a few possible responses. * Exact Match @@ -68,53 +106,108 @@ Generally when using a valid ULN there should be no issues with this request, bu * Learner opted to not share data * Learner could not be verified -Example JSON Body +Example request body: ```json { - "givenName": "Connor", - "familyName": "Carroll", - "uln": "4444599390" + "givenName": "Sean", + "familyName": "Findlay", + "uln": "1174112637", + "dateOfBirth": "1980-11-01", + "gender": 1 } ``` -Example JSON Response +Example response body: ```json { - "responseCode": "WSRC0004", - "foundUln": "6936002314", - "incomingUln": "4444599390", + "searchParameters": { + "givenName": "Sean", + "familyName": "Findlay", + "uln": "1174112637", + "dateOfBirth": "1980-11-01", + "gender": 1 + }, + "responseType": "Exact Match", + "foundUln": "1174112637", + "incomingUln": "1174112637", "learnerRecord": [ { - "id": "1234", - "achievementProviderUkprn": "11111112", - "achievementProviderName": "PRIMARY SCHOOL", + "id": "2931", + "achievementProviderUkprn": "10030488", + "achievementProviderName": "LUTON PENTECOSTAL CHURCH", "awardingOrganisationName": "UNKNOWN", - "qualificationType": "NVQ/GNVQ Key Skills Unit", - "subjectCode": "1000123A", - "achievementAwardDate": "2010-01-01", + "qualificationType": "GCSE", + "subjectCode": "50079116", + "achievementAwardDate": "2011-10-24", "credits": "0", "source": "ILR", "dateLoaded": "2012-05-31 16:47:04", "underDataChallenge": "N", "level": "", "status": "F", - "subject": "Key Skills", + "subject": "GCSE in English Literature", "grade": "9999999999", "awardingOrganisationUkprn": "UNKNWN", "collectionType": "W", "returnNumber": "02", - "participationStartDate": "2010-09-01", - "participationEndDate": "2010-09-26" + "participationStartDate": "2011-10-02", + "participationEndDate": "2011-10-24" } ] } ``` +Response codes: +* 200 - Success +* 400 - Bad Request, malformed inputs +* 401 - Unauthorised +* 403 - Forbidden +--- ## API Documentation -API documentation is available here - http://localhost:8080/swagger-ui/index.html +OpenAPI documentation is available here - http://localhost:8080/swagger-ui/index.html + +--- + +## Authentication -## Running the application while developing +The hmpps-learner-records-api requires bearer authorization. + +The tokens for authenticating with this service need to be requested from hmpps-auth via a `basic` authentication, citing your service's client id and client secret. + +You should ensure your service has the appropriate roles present to access this service. + +Example header +``` +"Authorization": "bearer " +``` + +--- + +## Running the application + +The hmpps-learner-records-api is generally run via docker so ensure you have docker or docker desktop installed and running. + +### Profiles + +When run on local developer machines there are two profiles available to run with that include varying levels of supporting services. + +#### `local` + +This will set up all services required to access the API as local instances and will ensure connections between those within the docker container. +There should be no external connections made. + +Services run: +1. HMPPS-Auth (for authentication) +2. Wiremock API (for LRS) +3. hmpps-learner-records-api (this service) + +#### `development` + +Running in the development profile will only spin up the hmpps-learner-records-api and make connections to the dev environment hosted versions of hmpps-auth and the DfE LRS environment. + +Services run: +1. hmpps-learner-records-api (this service) ### Environment Variables @@ -132,20 +225,17 @@ SPRING_PROFILES_ACTIVE= In order to make a connection to the LRS Development environment (achieved when using the `development` profile) you will require the relevant certificate. -Details for acquiring this certificate can be found [here](https://github.com/moj-analytical-services/dmet-bold/wiki/RR-Pilot-%E2%80%90-LRS-Microservice). +Reach out to the development team if you don't have this. Once downloaded, add the `WebServiceClientCert.pfx` to the project root directory. -Once downloaded, add the `WebServiceClientCert.pfx` to the root project directory. - - -### Starting the service - -Insert these two lines into the Dockerfile if running locally (as well as ensuring you have the SSL Certificate in the root directory): +Uncomment these two lines in the Dockerfile when running locally: ``` COPY WebServiceClientCert.pfx /app/WebServiceClientCert.pfx RUN ls -la /app/WebServiceClientCert.pfx ``` -Use the docker compose file to start the services - HMPPS-Auth, Wiremock for mocking LRS API Response and this microservice. +### Starting the service + +Use the docker compose file to start the services including your chosen profile. Run: ```bash @@ -153,37 +243,22 @@ docker-compose down docker-compose --profile= --env-file .env. up --build ``` -As mentioned before there are two profiles. - -`local` will run: -1. HMPPS-Auth - Use the guidance [here](https://github.com/moj-analytical-services/dmet-bold/wiki/RR-Pilot-%E2%80%90-LRS-API-%E2%80%90-HMPPS-Auth#make-oauth-request) -to get the access_token from the local instance of `hmpps-auth`. -2. Wiremock API -3. This Service - -Or `development` will run: -1. This Service - -_Instead of the wiremock API, this profile will attempt connection to the LRS Dev environment. This profile will also -require you to connect to the `hmpps-auth` Dev environment._ - -Follow the guidance [here](https://github.com/moj-analytical-services/dmet-bold/wiki/RR-Pilot-%E2%80%90-LRS-API-%E2%80%90-HMPPS-Auth#client-credentials) -to get the access_token from the `hmpps-auth` Dev environment. - -E.g. +Local: ```bash docker-compose --profile=local --env-file .env.local up --build ``` -or +Development: ```bash docker-compose --profile=development --env-file .env.development up --build ``` +--- + ## Running tests: Ensure no docker services are running as there may be port collisions. -## Testing in a terminal: +### Testing in a terminal: Open a terminal either in IntelliJ or in a separate window, ensuring you are in the repo directory. @@ -198,7 +273,7 @@ Run the following command: ./gradlew test ``` -## Testing using IntelliJ: +### Testing using IntelliJ: If you encounter issues, make sure gradle is set up properly in IntelliJ for this project. diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/openapi/FindByULNApi.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/openapi/FindByULNApi.kt index c6aef80..dbb00d0 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/openapi/FindByULNApi.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/openapi/FindByULNApi.kt @@ -7,8 +7,7 @@ import io.swagger.v3.oas.annotations.media.Schema import io.swagger.v3.oas.annotations.parameters.RequestBody import io.swagger.v3.oas.annotations.responses.ApiResponse import io.swagger.v3.oas.annotations.security.SecurityRequirement -import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.request.GetPLRByULNRequest -import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.response.FindLearnerByDemographicsResponse +import uk.gov.justice.digital.hmpps.learnerrecordsapi.models.response.GetPLRByULNResponse import uk.gov.justice.hmpps.kotlin.common.ErrorResponse @Target(AnnotationTarget.FUNCTION) @@ -28,9 +27,11 @@ import uk.gov.justice.hmpps.kotlin.common.ErrorResponse name = "Example Request", value = """ { - "givenName": "Connor", - "familyName": "Carroll", - "uln": "4444599390" + "givenName": "Sean", + "familyName": "Findlay", + "uln": "1174112637", + "dateOfBirth": "1980-11-01", + "gender": 1 } """, ), @@ -46,7 +47,121 @@ import uk.gov.justice.hmpps.kotlin.common.ErrorResponse content = [ Content( mediaType = "application/json", - schema = Schema(implementation = FindLearnerByDemographicsResponse::class), + schema = Schema(implementation = GetPLRByULNResponse::class), + examples = [ + ExampleObject( + name = "Exact Match Response", + value = """ + { + "searchParameters": { + "givenName": "Sean", + "familyName": "Findlay", + "uln": "1174112637", + "dateOfBirth": "1980-11-01", + "gender": 1 + }, + "responseType": "Exact Match", + "foundUln": "1174112637", + "incomingUln": "1174112637", + "learnerRecord": [ + { + "id": "2931", + "achievementProviderUkprn": "10030488", + "achievementProviderName": "LUTON PENTECOSTAL CHURCH", + "awardingOrganisationName": "UNKNOWN", + "qualificationType": "GCSE", + "subjectCode": "50079116", + "achievementAwardDate": "2011-10-24", + "credits": "0", + "source": "ILR", + "dateLoaded": "2012-05-31 16:47:04", + "underDataChallenge": "N", + "level": "", + "status": "F", + "subject": "GCSE in English Literature", + "grade": "9999999999", + "awardingOrganisationUkprn": "UNKNWN", + "collectionType": "W", + "returnNumber": "02", + "participationStartDate": "2011-10-02", + "participationEndDate": "2011-10-24" + } + ] + } + """, + ), + ExampleObject( + name = "Linked Learner Match Response", + value = """ + { + "searchParameters": { + "givenName": "Connor", + "familyName": "Carroll", + "uln": "4444599390" + }, + "responseType": "Linked Learner Match", + "foundUln": "6936002314", + "incomingUln": "4444599390", + "learnerRecord": [ + { + "id": "4284", + "achievementProviderUkprn": "10032743", + "achievementProviderName": "PRIORSLEE PRIMARY SCHOOL ACADEMY TRUST", + "awardingOrganisationName": "UNKNOWN", + "qualificationType": "NVQ/GNVQ Key Skills Unit", + "subjectCode": "1000323X", + "achievementAwardDate": "2010-09-26", + "credits": "0", + "source": "ILR", + "dateLoaded": "2012-05-31 16:47:04", + "underDataChallenge": "N", + "level": "", + "status": "F", + "subject": "Key Skills in Application of Number - level 1", + "grade": "9999999999", + "awardingOrganisationUkprn": "UNKNWN", + "collectionType": "W", + "returnNumber": "02", + "participationStartDate": "2010-09-01", + "participationEndDate": "2010-09-26" + }, + ] + } + """, + ), + ExampleObject( + name = "Learner opted to not share data Response", + value = """ + { + "searchParameters": { + "givenName": "John", + "familyName": "Smith", + "uln": "1026922983" + }, + "responseType": "Learner opted to not share data", + "foundUln": "1026922983", + "incomingUln": "1026922983", + "learnerRecord": [] + } + """, + ), + ExampleObject( + name = "Learner could not be verified Response", + value = """ + { + "searchParameters": { + "givenName": "John", + "familyName": "Smith", + "uln": "1174112637" + }, + "responseType": "Learner could not be verified", + "foundUln": "", + "incomingUln": "1174112637", + "learnerRecord": [] + } + """, + ), + ], ), ], ), diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/resource/PLRResource.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/resource/PLRResource.kt index 929fb9c..ec37e0f 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/resource/PLRResource.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/learnerrecordsapi/resource/PLRResource.kt @@ -1,5 +1,6 @@ package uk.gov.justice.digital.hmpps.learnerrecordsapi.resource +import io.swagger.v3.oas.annotations.tags.Tag import jakarta.validation.Valid import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.PostMapping @@ -17,6 +18,7 @@ class PLRResource( ) : BaseResource() { @PostMapping + @Tag(name = "PLR") @FindByULNApi suspend fun findByUln( @RequestBody @Valid getPLRByULNRequest: uk.gov.justice.digital.hmpps.learnerrecordsapi.models.request.GetPLRByULNRequest,