From 672765a3cead420cd95d8a8cc1b0a8a8f2bf4b0b Mon Sep 17 00:00:00 2001 From: Jon Brighton Date: Mon, 13 Jan 2025 12:35:57 +0000 Subject: [PATCH] CDPS-1086: Setting up first deploy --- .sdkmanrc | 3 + README.md | 141 ++---------------- .../0000-record-architecture-decisions.md | 87 +++++++++++ .../0001-prefer-prisoner-over-offender.md | 26 ++++ .../0002-table-names-should-be-singular.md | 29 ++++ architecture-decision-record/9999-end.md | 3 + architecture-decision-record/README.md | 19 +++ .../values.yaml | 12 +- helm_deploy/values-dev.yaml | 11 +- helm_deploy/values-preprod.yaml | 5 +- helm_deploy/values-prod.yaml | 3 - readme/build_test_run.md | 101 +++++++++++++ readme/sdkman.md | 24 +++ .../config/WebClientConfiguration.kt | 14 -- .../health/HealthPingCheck.kt | 8 +- .../resource/ExampleResource.kt | 2 +- .../service/ExampleApiService.kt | 30 +--- src/main/resources/application-dev.yml | 23 ++- src/main/resources/application.yml | 32 ++-- src/main/resources/banner.txt | 25 ++-- .../integration/ExampleResourceIntTest.kt | 25 +--- .../integration/IntegrationTestBase.kt | 5 +- .../integration/health/HealthCheckTest.kt | 1 - .../wiremock/ExampleApiMockServer.kt | 58 ------- src/test/resources/application-test.yml | 20 ++- 25 files changed, 380 insertions(+), 327 deletions(-) create mode 100644 .sdkmanrc create mode 100644 architecture-decision-record/0000-record-architecture-decisions.md create mode 100644 architecture-decision-record/0001-prefer-prisoner-over-offender.md create mode 100644 architecture-decision-record/0002-table-names-should-be-singular.md create mode 100644 architecture-decision-record/9999-end.md create mode 100644 architecture-decision-record/README.md create mode 100644 readme/build_test_run.md create mode 100644 readme/sdkman.md delete mode 100644 src/test/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/integration/wiremock/ExampleApiMockServer.kt diff --git a/.sdkmanrc b/.sdkmanrc new file mode 100644 index 0000000..d2635ab --- /dev/null +++ b/.sdkmanrc @@ -0,0 +1,3 @@ +# Enable auto-env through the sdkman_auto_env config +# Add key=value pairs of SDKs to use below +java=21.0.5-tem diff --git a/README.md b/README.md index d6abd89..1608324 100644 --- a/README.md +++ b/README.md @@ -1,136 +1,13 @@ -# hmpps-health-and-medication-api - +# HMPPS Health and Medication API [![repo standards badge](https://img.shields.io/badge/endpoint.svg?&style=flat&logo=github&url=https%3A%2F%2Foperations-engineering-reports.cloud-platform.service.justice.gov.uk%2Fapi%2Fv1%2Fcompliant_public_repositories%2Fhmpps-health-and-medication-api)](https://operations-engineering-reports.cloud-platform.service.justice.gov.uk/public-report/hmpps-health-and-medication-api "Link to report") -[![Docker Repository on ghcr](https://img.shields.io/badge/ghcr.io-repository-2496ED.svg?logo=docker)](https://ghcr.io/ministryofjustice/hmpps-health-and-medication-api) -[![API docs](https://img.shields.io/badge/API_docs_-view-85EA2D.svg?logo=swagger)](https://hmpps-health-and-medication-api-dev.hmpps.service.justice.gov.uk/webjars/swagger-ui/index.html?configUrl=/v3/api-docs) - -Template github repo used for new Kotlin based projects. - -# Instructions - -If this is a HMPPS project then the project will be created as part of bootstrapping - -see [dps-project-bootstrap](https://github.com/ministryofjustice/dps-project-bootstrap). You are able to specify a -template application using the `github_template_repo` attribute to clone without the need to manually do this yourself -within GitHub. - -This project is community managed by the mojdt `#kotlin-dev` slack channel. -Please raise any questions or queries there. Contributions welcome! - -Our security policy is located [here](https://github.com/ministryofjustice/hmpps-health-and-medication-api/security/policy). - -Documentation to create new service is located [here](https://tech-docs.hmpps.service.justice.gov.uk/applicationplatform/newservice-GHA/). - -## Creating a Cloud Platform namespace - -When deploying to a new namespace, you may wish to use the -[templates project namespace](https://github.com/ministryofjustice/cloud-platform-environments/tree/main/namespaces/live.cloud-platform.service.justice.gov.uk/hmpps-templates-dev) -as the basis for your new namespace. This namespace contains both the kotlin and typescript template projects, -which is the usual way that projects are setup. - -Copy this folder and update all the existing namespace references to correspond to the environment to which you're deploying. - -If you only need the kotlin configuration then remove all typescript references and remove the elasticache configuration. - -To ensure the correct github teams can approve releases, you will need to make changes to the configuration in `resources/service-account-github` where the appropriate team names will need to be added (based on [lines 98-100](https://github.com/ministryofjustice/cloud-platform-environments/blob/main/namespaces/live.cloud-platform.service.justice.gov.uk/hmpps-templates-dev/resources/serviceaccount-github.tf#L98) and the reference appended to the teams list below [line 112](https://github.com/ministryofjustice/cloud-platform-environments/blob/main/namespaces/live.cloud-platform.service.justice.gov.uk/hmpps-templates-dev/resources/serviceaccount-github.tf#L112)). Note: hmpps-sre is in this list to assist with deployment issues. - -Submit a PR to the Cloud Platform team in -#ask-cloud-platform. Further instructions from the Cloud Platform team can be found in -the [Cloud Platform User Guide](https://user-guide.cloud-platform.service.justice.gov.uk/#cloud-platform-user-guide) - -## Renaming from HMPPS Health And Medication Api - github Actions - -Once the new repository is deployed. Navigate to the repository in github, and select the `Actions` tab. -Click the link to `Enable Actions on this repository`. - -Find the Action workflow named: `rename-project-create-pr` and click `Run workflow`. This workflow will -execute the `rename-project.bash` and create Pull Request for you to review. Review the PR and merge. - -Note: ideally this workflow would run automatically however due to a recent change github Actions are not -enabled by default on newly created repos. There is no way to enable Actions other then to click the button in the UI. -If this situation changes we will update this project so that the workflow is triggered during the bootstrap project. -Further reading: - -The script takes six arguments: - -### New project name - -This should start with `hmpps-` e.g. `hmpps-prison-visits` so that it can be easily distinguished in github from -other departments projects. Try to avoid using abbreviations so that others can understand easily what your project is. - -### Slack channel for release notifications - -By default, release notifications are only enabled for production. The circleci configuration can be amended to send -release notifications for deployments to other environments if required. Note that if the configuration is amended, -the slack channel should then be amended to your own team's channel as `dps-releases` is strictly for production release -notifications. If the slack channel is set to something other than `dps-releases`, production release notifications -will still automatically go to `dps-releases` as well. This is configured by `releases-slack-channel` in -`.circleci/config.yml`. - -### Slack channel for pipeline security notifications - -Ths channel should be specific to your team and is for daily / weekly security scanning job results. It is your team's -responsibility to keep up-to-date with security issues and update your application so that these jobs pass. You will -only be notified if the jobs fail. The scan results can always be found in circleci for your project. This is -configured by `alerts-slack-channel` in `.circleci/config.yml`. - -### Non production kubernetes alerts - -By default Prometheus alerts are created in the application namespaces to monitor your application e.g. if your -application is crash looping, there are a significant number of errors from the ingress. Since Prometheus runs in -cloud platform AlertManager needs to be setup first with your channel. Please see -[Create your own custom alerts](https://user-guide.cloud-platform.service.justice.gov.uk/documentation/monitoring-an-app/how-to-create-alarms.html) -in the Cloud Platform user guide. Once that is setup then the `custom severity label` can be used for -`alertSeverity` in the `helm_deploy/values-*.yaml` configuration. - -Normally it is worth setting up two separate labels and therefore two separate slack channels - one for your production -alerts and one for your non-production alerts. Using the same channel can mean that production alerts are sometimes -lost within non-production issues. - -### Production kubernetes alerts - -This is the severity label for production, determined by the `custom severity label`. See the above -#non-production-kubernetes-alerts for more information. This is configured in `helm_deploy/values-prod.yaml`. - -### Product ID - -This is so that we can link a component to a product and thus provide team and product information in the Developer -Portal. Refer to the developer portal at https://developer-portal.hmpps.service.justice.gov.uk/products to find your -product id. This is configured in `helm_deploy//values.yaml`. - -## Manually branding from template app - -Run the `rename-project.bash` without any arguments. This will prompt for the six required parameters and create a PR. -The script requires a recent version of `bash` to be installed, as well as GNU `sed` in the path. - -## TODOs and Examples - -We have tried to provide some examples of best practice in the application - so there are lots of TODOs in the code -where changes are required to meet your requirements. There is an `ExampleResource` that includes best practice and also -serve as spring security examples. The template typescript project has a demonstration that calls this endpoint as well. - -For the demonstration, rather than introducing a dependency on a different service, this application calls out to -itself. This is only to show a service calling out to another service and is certainly not recommended! - -## Running the application locally - -The application comes with a `dev` spring profile that includes default settings for running locally. This is not -necessary when deploying to kubernetes as these values are included in the helm configuration templates - -e.g. `values-dev.yaml`. - -There is also a `docker-compose.yml` that can be used to run a local instance of the template in docker and also an -instance of HMPPS Auth (required if your service calls out to other services using a token). - -```bash -docker compose pull && docker compose up -``` - -will build the application and run it and HMPPS Auth within a local docker instance. +[![CircleCI](https://circleci.com/gh/ministryofjustice/hmpps-health-and-medication-api/tree/main.svg?style=svg)](https://circleci.com/gh/ministryofjustice/hmpps-health-and-medication-api) +[![codecov](https://codecov.io/github/ministryofjustice/hmpps-health-and-medication-api/branch/main/graph/badge.svg)](https://codecov.io/github/ministryofjustice/hmpps-health-and-medication-api) +[![Docker Repository on Quay](https://img.shields.io/badge/quay.io-repository-2496ED.svg?logo=docker)](https://quay.io/repository/hmpps/hmpps-health-and-medication-api) +[![API docs](https://img.shields.io/badge/API_docs_-view-85EA2D.svg?logo=swagger)](https://health-and-medication-api-dev.prison.service.justice.gov.uk/swagger-ui/index.html) -### Running the application in Intellij +This is a Spring Boot service, written in Kotlin, which owns a subset of data about a person in prison (migrated from NOMIS). -```bash -docker compose pull && docker compose up --scale hmpps-health-and-medication-api=0 -``` +## Contents -will just start a docker instance of HMPPS Auth. The application should then be started with a `dev` active profile -in Intellij. +1. [Building, Testing and Running](readme/build_test_run.md) +2. [Architecture Decision Records](architecture-decision-record/README.md) diff --git a/architecture-decision-record/0000-record-architecture-decisions.md b/architecture-decision-record/0000-record-architecture-decisions.md new file mode 100644 index 0000000..3d1d483 --- /dev/null +++ b/architecture-decision-record/0000-record-architecture-decisions.md @@ -0,0 +1,87 @@ +[Contents](README.md) + +# 0. Record architecture decisions + +Date: 2025-01-10 + +## Status + +✅ Accepted + +## Context + +When making architectural decisions, we should record them somewhere for future reference, to help us remember why we made them, and +to help teams working in related areas understand why we made them. + +We should make our decisions public so that other teams can find them more easily, and +because [making things open makes things better](https://www.gov.uk/guidance/government-design-principles#make-things-open-it-makes-things-better) +. + +## Decision + +We will use Architecture Decision Records, as described by Michael Nygard in +[this article](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions) + +An architecture decision record is a short text file describing a single decision. + +We will keep ADRs in this public repository under decisions/[number]-[title].md + +We should use a lightweight text formatting language like Markdown. + +ADRs will be numbered sequentially and monotonically. Numbers will not be reused. + +If a decision is reversed, we will keep the old one around, but mark it as superseded. (It's still relevant to know that +it was the decision, but is no longer the decision.) + +We will use a format with just a few parts, so each document is easy to digest: + +**Title** These documents have names that are short noun phrases. For example, +"ADR 1: Record architectural decisions" or "ADR 9: Use Docker for deployment" + +**Status** A decision may be "proposed" if it's still under discussion, or +"accepted" once it is agreed. If a later ADR changes or reverses a decision, it may be marked as "deprecated" or " +superseded" with a reference to its replacement. + +**Context** This section describes the forces at play, including technological, political, social, and local to the +service. These forces are probably in tension, and should be called out as such. The language in this section is +value-neutral. It is simply describing facts. + +**Decision** This section describes our response to these forces. It is stated in full sentences, with active voice. "We +will ..." + +**Consequences** This section describes the resulting context, after applying the decision. All consequences should be +listed here, not just the "positive" +ones. A particular decision may have positive, negative, and neutral consequences, but all of them affect the team and +service in the future. + +The whole document should be one or two pages long. We will write each ADR as if it is a conversation with a future +person joining the team. This requires good writing style, with full sentences organised into paragraphs. Bullets are +acceptable only for visual style, not as an excuse for writing sentence fragments. + +[adr-tools](https://github.com/npryce/adr-tools) can help us work with our ADRs consistently. + +We will link to these ADRs from other documentation where relevant. + +## Consequences + +One ADR describes one significant decision for the service. It should be something that has an effect on how the rest of +the service will run. + +Developers and service stakeholders (and anyone else who's interested) can see the ADRs, even as the team composition +changes over time. + +The motivation behind previous decisions is visible for everyone, present and future. Nobody is left scratching their +heads to understand, "What were they thinking?" and the time to change old decisions will be clear from changes in the +service's context. + +Having a central place to record decisions which affect all of our work will make the sequence of decisions clear, and +make it easier for us to refer back to decisions later on. + +This repo holds Architecture Decision Records, for the PCMS team. We will be using the architecture decision record to +help keep a record of what approaches we are currently taking to our infrastructure and to help our future selves +understand why those decisions were made. The approach is described by Michael Nygard +in [this article](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). + +Everyone can see the ADRs, even as the team composition changes over time. This means motivation behind previous +decisions is visible for everyone, present and future. Nobody is left scratching their heads to understand, "What were +they thinking?" and the time to change old decisions will be clear from changes in the project's context. diff --git a/architecture-decision-record/0001-prefer-prisoner-over-offender.md b/architecture-decision-record/0001-prefer-prisoner-over-offender.md new file mode 100644 index 0000000..0a87b6d --- /dev/null +++ b/architecture-decision-record/0001-prefer-prisoner-over-offender.md @@ -0,0 +1,26 @@ +[Contents](README.md) +[Next >](9999-end.md) + +# 1. Prefer 'Prisoner' Over 'Offender' + +Date: 2025-01-10 + +## Status + +✅ Accepted + +## Context + +The terms 'offender' and 'prisoner' are often used interchangeably but this causes issues when trying to remember if in +a particular bit of code we have decided to use one term or another. + +## Decision + +We will prefer to use the term 'prisoner' wherever possible - this service is inherently prisoner focussed. + +## Consequences + +This should bring much more consistency to the codebase and make it easier to develop. There will no longer be an issue +deciding what to name a file, class, method etc. + + diff --git a/architecture-decision-record/0002-table-names-should-be-singular.md b/architecture-decision-record/0002-table-names-should-be-singular.md new file mode 100644 index 0000000..22ac24d --- /dev/null +++ b/architecture-decision-record/0002-table-names-should-be-singular.md @@ -0,0 +1,29 @@ +[Contents](README.md) +[Next >](9999-end.md) + +# 2. Database table names should be singular + +Date: 2025-01-10 + +## Status + +✅ Accepted + +## Context + +Table names can be either singular (i.e. identifying_mark) or plural (i.e. identifying_marks). There are mixed opinions +about which it should be. + +## Decision + +We are opting to use singular table names. This is mostly a decision made for consistency, but there are a couple of +other benefits: +* The JPA entities don't need an `@Table` annotation to specify the plural table name +* It keeps all the JPA naming consistent, and we don't need to worry about the oddities of the English language +such as irregular plurals. + +## Consequences + +We need to correct some existing tables to conform with this approach. + + diff --git a/architecture-decision-record/9999-end.md b/architecture-decision-record/9999-end.md new file mode 100644 index 0000000..b014c68 --- /dev/null +++ b/architecture-decision-record/9999-end.md @@ -0,0 +1,3 @@ +No more records + +[Back to Contents](README.md) diff --git a/architecture-decision-record/README.md b/architecture-decision-record/README.md new file mode 100644 index 0000000..a26973a --- /dev/null +++ b/architecture-decision-record/README.md @@ -0,0 +1,19 @@ +# Health and Medication Architecture Decisions + +This is a record of architectural decisions made during the development of the Health and Medication API project. + +To understand why we are recording decisions and how we are doing it, please +see [Record Architecture Decisions](architecture-decision-record/0000-record-architecture-decisions.md) + +## Table of contents + +* ✅ [1. Prefer 'Prisoner' over 'Offender'](0001-prefer-prisoner-over-offender.md) +* ✅ [2. Database table names should be singular](0002-table-names-should-be-singular.md) + +### Statuses: + +* Proposed: 🤔 +* Accepted: ✅ +* Rejected: ❌ +* Superseded: ⌛️ +* Amended: ♻️ diff --git a/helm_deploy/hmpps-health-and-medication-api/values.yaml b/helm_deploy/hmpps-health-and-medication-api/values.yaml index bf23189..0b986c9 100644 --- a/helm_deploy/hmpps-health-and-medication-api/values.yaml +++ b/helm_deploy/hmpps-health-and-medication-api/values.yaml @@ -1,6 +1,6 @@ generic-service: nameOverride: hmpps-health-and-medication-api - productId: "DPS013" # productId for the product that this belongs too, i.e. DPS001, see README.md for details + productId: "DPS013" replicaCount: 4 @@ -26,13 +26,15 @@ generic-service: # [name of environment variable as seen by app]: [key of kubernetes secret to load] namespace_secrets: - hmpps-health-and-medication-api: - # Example client registration secrets - EXAMPLE_API_CLIENT_ID: "TEMPLATE_KOTLIN_API_CLIENT_ID" - EXAMPLE_API_CLIENT_SECRET: "TEMPLATE_KOTLIN_API_CLIENT_SECRET" application-insights: APPLICATIONINSIGHTS_CONNECTION_STRING: "APPLICATIONINSIGHTS_CONNECTION_STRING" + rds-postgresql-instance-output: + DATABASE_ENDPOINT: "rds_instance_endpoint" + DATABASE_NAME: "database_name" + DATABASE_USERNAME: "database_username" + DATABASE_PASSWORD: "database_password" + allowlist: groups: - internal diff --git a/helm_deploy/values-dev.yaml b/helm_deploy/values-dev.yaml index 1427b76..42b8387 100644 --- a/helm_deploy/values-dev.yaml +++ b/helm_deploy/values-dev.yaml @@ -10,11 +10,16 @@ generic-service: env: APPLICATIONINSIGHTS_CONFIGURATION_FILE: "applicationinsights.dev.json" HMPPS_AUTH_URL: "https://sign-in-dev.hmpps.service.justice.gov.uk/auth" - # Template kotlin calls out to itself to provide an example of a service call - # TODO: This should be replaced by a call to a different service, or removed - EXAMPLE_API_URL: "https://health-and-medication-api-dev.hmpps.service.justice.gov.uk" + + scheduledDowntime: + enabled: true + startup: '30 6 * * 1-5' # Start at 6.30am UTC Monday-Friday + shutdown: '30 21 * * 1-5' # Stop at 9.30pm UTC Monday-Friday # 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 generic-prometheus-alerts: alertSeverity: hmpps-prisoner-profile-non-prod + businessHoursOnly: true + rdsAlertsDatabases: + cloud-platform-74e3c6cbeecd38ba: "HMPPS Health and Medication DB (dev)" diff --git a/helm_deploy/values-preprod.yaml b/helm_deploy/values-preprod.yaml index 1c88f95..87af626 100644 --- a/helm_deploy/values-preprod.yaml +++ b/helm_deploy/values-preprod.yaml @@ -10,11 +10,8 @@ generic-service: env: APPLICATIONINSIGHTS_CONFIGURATION_FILE: "applicationinsights.dev.json" HMPPS_AUTH_URL: "https://sign-in-preprod.hmpps.service.justice.gov.uk/auth" - # Template kotlin calls out to itself to provide an example of a service call - # TODO: This should be replaced by a call to a different service, or removed - EXAMPLE_API_URL: "https://health-and-medication-api-preprod.hmpps.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 generic-prometheus-alerts: - alertSeverity: hmpps-prisoner-profile-non-prod + alertSeverity: hmpps-prisoner-profile-non-prod \ No newline at end of file diff --git a/helm_deploy/values-prod.yaml b/helm_deploy/values-prod.yaml index a690fd0..f3444e8 100644 --- a/helm_deploy/values-prod.yaml +++ b/helm_deploy/values-prod.yaml @@ -7,9 +7,6 @@ generic-service: env: HMPPS_AUTH_URL: "https://sign-in.hmpps.service.justice.gov.uk/auth" - # Template kotlin calls out to itself to provide an example of a service call - # TODO: This should be replaced by a call to a different service, or removed - EXAMPLE_API_URL: "https://health-and-medication-api.hmpps.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 diff --git a/readme/build_test_run.md b/readme/build_test_run.md new file mode 100644 index 0000000..11d67e7 --- /dev/null +++ b/readme/build_test_run.md @@ -0,0 +1,101 @@ +[< Back](../README.md) +--- + +## Building + +To use the same version of Java locally as is used in CI and production, follow [these notes](sdkman.md). + + +Firstly build the project (without tests) by running: +``` +./gradlew clean build -x test +``` + +To rebuild the docker image locally after building the project (perhaps after some new changes), run: +``` +docker build -t quay.io/hmpps/hmpps-health-and-medication-api:latest . +``` + +## Testing +``` +./gradlew test +``` + +## Running Locally + +First, start the database and other required services via docker-compose with: + +```shell +docker compose -f docker-compose-local.yml up +``` + +The service can then be run in the following ways: + +### Running in the command line with gradle +``` +./gradlew bootRun --args='--spring.profiles.active=dev' +``` + +### Running in IntelliJ +Run the main class with the following VM options: +``` +-Dspring.profiles.active=dev +``` + +In both of the above sections, `dev` can be replaced with `dev-postgres` to run against the postgres docker container +instead of h2. + +### Running with docker-compose +``` +docker-compose up +``` + +## Common gradle tasks + +To list project dependencies, run: + +``` +./gradlew dependencies +``` + +To check for dependency updates, run: +``` +./gradlew dependencyUpdates --warning-mode all +``` + +To run an OWASP dependency check, run: +``` +./gradlew clean dependencyCheckAnalyze --info +``` + +To upgrade the gradle wrapper version, run: +``` +./gradlew wrapper --gradle-version= +``` + +To automatically update project dependencies, run: +``` +./gradlew useLatestVersions +``` + +#### Ktlint Gradle Tasks + +To run Ktlint check: +``` +./gradlew ktlintCheck +``` + +To run Ktlint format: +``` +./gradlew ktlintFormat +``` + +To register pre-commit check to run Ktlint format: +``` +./gradlew addKtlintFormatGitPreCommitHook +``` + +...or to register pre-commit check to only run Ktlint check: +``` +./gradlew addKtlintCheckGitPreCommitHook +``` \ No newline at end of file diff --git a/readme/sdkman.md b/readme/sdkman.md new file mode 100644 index 0000000..93c79f1 --- /dev/null +++ b/readme/sdkman.md @@ -0,0 +1,24 @@ +[< Back](./building_and_running.md) +--- + +## sdkman + +It is recommended to use [sdkman](https://sdkman.io/) to ensure that you are running the same version of Java that is +used in CI and in production. + +To do this on a mac: + +```shell +curl -s "https://get.sdkman.io" | bash +``` + +Close the terminal and reopen. Then run **within the repository folder**: + +```shell +sdk env +``` + +This will pick up the `.sdkmanrc` file which contains the java version that should be used. + +To enable sdkman to automatically switch you to the correct java version when changing directory in the terminal, +edit `~/.sdkman/etc/config` and set `sdkman_auto_env=true` diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/config/WebClientConfiguration.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/config/WebClientConfiguration.kt index e6bf3b3..9696526 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/config/WebClientConfiguration.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/config/WebClientConfiguration.kt @@ -3,30 +3,16 @@ package uk.gov.justice.digital.hmpps.healthandmedicationapi.config import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager import org.springframework.web.reactive.function.client.WebClient -import uk.gov.justice.hmpps.kotlin.auth.authorisedWebClient import uk.gov.justice.hmpps.kotlin.auth.healthWebClient import java.time.Duration @Configuration class WebClientConfiguration( - @Value("\${example-api.url}") val exampleApiBaseUri: String, @Value("\${hmpps-auth.url}") val hmppsAuthBaseUri: String, @Value("\${api.health-timeout:2s}") val healthTimeout: Duration, @Value("\${api.timeout:20s}") val timeout: Duration, ) { - // HMPPS Auth health ping is required if your service calls HMPPS Auth to get a token to call other services - // TODO: Remove the health ping if no call outs to other services are made @Bean fun hmppsAuthHealthWebClient(builder: WebClient.Builder): WebClient = builder.healthWebClient(hmppsAuthBaseUri, healthTimeout) - - // TODO: This is an example health bean for checking other services and should be removed / replaced - @Bean - fun exampleApiHealthWebClient(builder: WebClient.Builder): WebClient = builder.healthWebClient(exampleApiBaseUri, healthTimeout) - - // TODO: This is an example bean for calling other services and should be removed / replaced - @Bean - fun exampleApiWebClient(authorizedClientManager: OAuth2AuthorizedClientManager, builder: WebClient.Builder): WebClient = - builder.authorisedWebClient(authorizedClientManager, registrationId = "example-api", url = exampleApiBaseUri, timeout) } diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/health/HealthPingCheck.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/health/HealthPingCheck.kt index cee923c..c8d4032 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/health/HealthPingCheck.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/health/HealthPingCheck.kt @@ -5,11 +5,5 @@ import org.springframework.stereotype.Component import org.springframework.web.reactive.function.client.WebClient import uk.gov.justice.hmpps.kotlin.health.HealthPingCheck -// HMPPS Auth health ping is required if your service calls HMPPS Auth to get a token to call other services -// TODO: Remove the health ping if no call outs to other services are made @Component("hmppsAuth") -class HmppsAuthHealthPing(@Qualifier("hmppsAuthHealthWebClient") webClient: WebClient) : HealthPingCheck(webClient) - -// TODO: Example ping health check calling out to other services -@Component("exampleApi") -class ExampleApiHealthPing(@Qualifier("exampleApiHealthWebClient") webClient: WebClient) : HealthPingCheck(webClient) +class HealthPingCheck(@Qualifier("hmppsAuthHealthWebClient") webClient: WebClient) : HealthPingCheck(webClient) diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/resource/ExampleResource.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/resource/ExampleResource.kt index 852ad6a..b7d5931 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/resource/ExampleResource.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/resource/ExampleResource.kt @@ -73,5 +73,5 @@ class ExampleResource(private val exampleApiService: ExampleApiService) { ), ], ) - fun getMessage(@PathVariable parameter: String) = exampleApiService.exampleGetExternalApiCall(parameter) + fun getMessage(@PathVariable parameter: String) = "{ message: \"Hello $parameter\"}" } diff --git a/src/main/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/service/ExampleApiService.kt b/src/main/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/service/ExampleApiService.kt index 133236f..c2c2230 100644 --- a/src/main/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/service/ExampleApiService.kt +++ b/src/main/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/service/ExampleApiService.kt @@ -1,37 +1,9 @@ package uk.gov.justice.digital.hmpps.healthandmedicationapi.service import org.springframework.stereotype.Service -import org.springframework.web.reactive.function.client.WebClient -import org.springframework.web.reactive.function.client.WebClientResponseException -import reactor.core.publisher.Mono import java.time.LocalDateTime -// This is an example of how to write a service calling out to another service. In this case we have wired up the -// kotlin template with itself so that the template doesn't depend on any other services. -// TODO: This is an example and should be renamed / replaced @Service -class ExampleApiService( - private val exampleApiWebClient: WebClient, -) { +class ExampleApiService { fun getTime(): LocalDateTime = LocalDateTime.now() - - fun exampleGetExternalApiCall(parameter: String): ExampleMessageDto? = - exampleApiWebClient.get() - // Note that we don't use string interpolation ("/${parameter}"). - // This is important - using string interpolation causes each uri to be added as a separate path in app - // insights and you'll run out of memory in your app. - // Also note that this is just an example and the /example-external-api endpoint doesn't exist in this kotlin - // template project so will return a not found response each time. - .uri("/example-external-api/{parameter}", parameter) - .retrieve() - .bodyToMono(ExampleMessageDto::class.java) - // if the endpoint returns a not found response (404) then treat as empty rather than throwing a server error - // other options would be to re-throw the not found and use the controller advice to return a 404 - .onErrorResume(WebClientResponseException.NotFound::class.java) { Mono.empty() } - .block() } - -// TODO: This is an example message and should be renamed / replaced -data class ExampleMessageDto( - val message: String, -) diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index 949a823..c302cdc 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -1,10 +1,19 @@ +server: + shutdown: immediate + hmpps-auth: url: "http://localhost:8090/auth" -# example client configuration for calling out to other services -# TODO: Remove / replace this configuration -example-api: - url: "http://localhost:8080" - client: - id: "example-api-client" - secret: "example-api-client-secret" +spring: + devtools: + add-properties: true + + datasource: + url: jdbc:postgresql://localhost:9432/health-and-medication-data?sslmode=prefer + + jpa: + show-sql: true + +database: + username: health-and-medication-data + password: health-and-medication-data diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 3bf3491..d888240 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,10 +1,11 @@ info.app: - name: HMPPS Health And Medication Api + name: HMPPS Health and Medication API version: 1.0 spring: application: name: hmpps-health-and-medication-api + codec: max-in-memory-size: 10MB @@ -13,27 +14,28 @@ spring: serialization: WRITE_DATES_AS_TIMESTAMPS: false - # TODO: This security section can be removed if your service doesn't call out to other services security: oauth2: resourceserver: jwt: jwk-set-uri: ${hmpps-auth.url}/.well-known/jwks.json - client: - provider: - hmpps-auth: - token-uri: ${hmpps-auth.url}/oauth/token + jpa: + open-in-view: false + show-sql: false + generate-ddl: false + hibernate: + ddl-auto: none - registration: - # example client registration for calling out to other services - # TODO: Remove / replace this registration - example-api: - provider: hmpps-auth - client-id: ${example-api.client.id} - client-secret: ${example-api.client.secret} - authorization-grant-type: client_credentials - scope: read + datasource: + url: 'jdbc:postgresql://${DATABASE_ENDPOINT}/${DATABASE_NAME}?sslmode=verify-full' + username: ${DATABASE_USERNAME} + password: ${DATABASE_PASSWORD} + hikari: + pool-name: HEALTH-AND-MEDICATION-DB-CP + maximum-pool-size: 10 + connection-timeout: 1000 + validation-timeout: 500 server: port: 8080 diff --git a/src/main/resources/banner.txt b/src/main/resources/banner.txt index fc59263..fdd5b1f 100644 --- a/src/main/resources/banner.txt +++ b/src/main/resources/banner.txt @@ -1,13 +1,12 @@ -_ _ _ _ ___ ___ ____ -|__| |\/| |__] |__] [__ -| | | | | | ___] - -___ ____ _ _ ___ _ ____ ___ ____ - | |___ |\/| |__] | |__| | |___ - | |___ | | | |___ | | | |___ - -_ _ ____ ___ _ _ _ _ -|_/ | | | | | |\ | -| \_ |__| | |___ | | \| - -TODO: Please change me by generating your own ASCII art and placing in banner.txt + _ _ __ __ _____ _____ _____ _ _ _ _ _ _ + | | | | \/ | __ \| __ \ / ____| | | | | | | | | | | | + | |__| | \ / | |__) | |__) | (___ | |__| | ___ __ _| | |_| |__ __ _ _ __ __| | + | __ | |\/| | ___/| ___/ \___ \ | __ |/ _ \/ _` | | __| '_ \ / _` | '_ \ / _` | + | | | | | | | | | | ____) | | | | | __/ (_| | | |_| | | | | (_| | | | | (_| | + |_| |_|_| |_|_| |_| |_____/ |_| |_|\___|\__,_|_|\__|_| |_| \__,_|_| |_|\__,_| + __ __ _ _ _ _ _____ _____ + | \/ | | (_) | | (_) /\ | __ \_ _| + | \ / | ___ __| |_ ___ __ _| |_ _ ___ _ __ / \ | |__) || | + | |\/| |/ _ \/ _` | |/ __/ _` | __| |/ _ \| '_ \ / /\ \ | ___/ | | + | | | | __/ (_| | | (_| (_| | |_| | (_) | | | | / ____ \| | _| |_ + |_| |_|\___|\__,_|_|\___\__,_|\__|_|\___/|_| |_| /_/ \_\_| |_____| diff --git a/src/test/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/integration/ExampleResourceIntTest.kt b/src/test/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/integration/ExampleResourceIntTest.kt index 023f48a..d280306 100644 --- a/src/test/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/integration/ExampleResourceIntTest.kt +++ b/src/test/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/integration/ExampleResourceIntTest.kt @@ -1,11 +1,9 @@ package uk.gov.justice.digital.hmpps.healthandmedicationapi.integration -import com.github.tomakehurst.wiremock.client.WireMock import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test -import uk.gov.justice.digital.hmpps.healthandmedicationapi.integration.wiremock.ExampleApiExtension.Companion.exampleApi import uk.gov.justice.digital.hmpps.healthandmedicationapi.integration.wiremock.HmppsAuthApiExtension.Companion.hmppsAuth import java.time.LocalDate @@ -95,7 +93,6 @@ class ExampleResourceIntTest : IntegrationTestBase() { @Test fun `should return OK`() { hmppsAuth.stubGrantToken() - exampleApi.stubExampleExternalApiUserMessage() webTestClient.get() .uri("/example/message/{parameter}", "bob") .headers(setAuthorisation(username = "AUTH_OK", roles = listOf("ROLE_TEMPLATE_KOTLIN__UI"))) @@ -103,27 +100,7 @@ class ExampleResourceIntTest : IntegrationTestBase() { .expectStatus() .isOk .expectBody() - .jsonPath("$.message").isEqualTo("A stubbed message") - - exampleApi.verify(WireMock.getRequestedFor(WireMock.urlEqualTo("/example-external-api/bob"))) - hmppsAuth.verify(1, WireMock.postRequestedFor(WireMock.urlEqualTo("/auth/oauth/token"))) - } - - @Test - fun `should return empty response if user not found`() { - hmppsAuth.stubGrantToken() - exampleApi.stubExampleExternalApiNotFound() - webTestClient.get() - .uri("/example/message/{parameter}", "bob") - .headers(setAuthorisation(username = "AUTH_NOTFOUND", roles = listOf("ROLE_TEMPLATE_KOTLIN__UI"))) - .exchange() - .expectStatus() - .isOk - .expectBody() - .jsonPath("$.message").doesNotExist() - - exampleApi.verify(WireMock.getRequestedFor(WireMock.urlEqualTo("/example-external-api/bob"))) - hmppsAuth.verify(1, WireMock.postRequestedFor(WireMock.urlEqualTo("/auth/oauth/token"))) + .jsonPath("$.message").isEqualTo("Hello bob") } } } diff --git a/src/test/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/integration/IntegrationTestBase.kt b/src/test/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/integration/IntegrationTestBase.kt index b81e643..b231409 100644 --- a/src/test/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/integration/IntegrationTestBase.kt +++ b/src/test/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/integration/IntegrationTestBase.kt @@ -7,13 +7,11 @@ import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDO import org.springframework.http.HttpHeaders import org.springframework.test.context.ActiveProfiles import org.springframework.test.web.reactive.server.WebTestClient -import uk.gov.justice.digital.hmpps.healthandmedicationapi.integration.wiremock.ExampleApiExtension -import uk.gov.justice.digital.hmpps.healthandmedicationapi.integration.wiremock.ExampleApiExtension.Companion.exampleApi import uk.gov.justice.digital.hmpps.healthandmedicationapi.integration.wiremock.HmppsAuthApiExtension import uk.gov.justice.digital.hmpps.healthandmedicationapi.integration.wiremock.HmppsAuthApiExtension.Companion.hmppsAuth import uk.gov.justice.hmpps.test.kotlin.auth.JwtAuthorisationHelper -@ExtendWith(HmppsAuthApiExtension::class, ExampleApiExtension::class) +@ExtendWith(HmppsAuthApiExtension::class) @SpringBootTest(webEnvironment = RANDOM_PORT) @ActiveProfiles("test") abstract class IntegrationTestBase { @@ -32,6 +30,5 @@ abstract class IntegrationTestBase { protected fun stubPingWithResponse(status: Int) { hmppsAuth.stubHealthPing(status) - exampleApi.stubHealthPing(status) } } diff --git a/src/test/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/integration/health/HealthCheckTest.kt b/src/test/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/integration/health/HealthCheckTest.kt index 68f0fcd..ce1c97c 100644 --- a/src/test/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/integration/health/HealthCheckTest.kt +++ b/src/test/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/integration/health/HealthCheckTest.kt @@ -30,7 +30,6 @@ class HealthCheckTest : IntegrationTestBase() { .expectBody() .jsonPath("status").isEqualTo("DOWN") .jsonPath("components.hmppsAuth.status").isEqualTo("DOWN") - .jsonPath("components.exampleApi.status").isEqualTo("DOWN") } @Test diff --git a/src/test/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/integration/wiremock/ExampleApiMockServer.kt b/src/test/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/integration/wiremock/ExampleApiMockServer.kt deleted file mode 100644 index b86e5c1..0000000 --- a/src/test/kotlin/uk/gov/justice/digital/hmpps/healthandmedicationapi/integration/wiremock/ExampleApiMockServer.kt +++ /dev/null @@ -1,58 +0,0 @@ -package uk.gov.justice.digital.hmpps.healthandmedicationapi.integration.wiremock - -import com.github.tomakehurst.wiremock.WireMockServer -import com.github.tomakehurst.wiremock.client.WireMock.aResponse -import com.github.tomakehurst.wiremock.client.WireMock.get -import com.github.tomakehurst.wiremock.client.WireMock.urlPathMatching -import org.junit.jupiter.api.extension.AfterAllCallback -import org.junit.jupiter.api.extension.BeforeAllCallback -import org.junit.jupiter.api.extension.BeforeEachCallback -import org.junit.jupiter.api.extension.ExtensionContext - -// TODO: Remove / replace this mock server as it currently calls the Example API (itself) -class ExampleApiMockServer : WireMockServer(8091) { - fun stubHealthPing(status: Int) { - stubFor( - get("/health/ping").willReturn( - aResponse() - .withHeader("Content-Type", "application/json") - .withBody("""{"status":"${if (status == 200) "UP" else "DOWN"}"}""") - .withStatus(status), - ), - ) - } - - fun stubExampleExternalApiUserMessage() { - stubFor( - get(urlPathMatching("/example-external-api/[a-zA-Z]*")) - .willReturn( - aResponse() - .withHeader("Content-Type", "application/json") - .withBody("""{ "message": "A stubbed message" }"""), - ), - ) - } - - fun stubExampleExternalApiNotFound() { - stubFor( - get(urlPathMatching("/example-external-api/[a-zA-Z]*")) - .willReturn( - aResponse() - .withStatus(404) - .withHeader("Content-Type", "application/json") - .withBody("""{ "userMessage": "A stubbed message" }"""), - ), - ) - } -} - -class ExampleApiExtension : BeforeAllCallback, AfterAllCallback, BeforeEachCallback { - companion object { - @JvmField - val exampleApi = ExampleApiMockServer() - } - - override fun beforeAll(context: ExtensionContext): Unit = exampleApi.start() - override fun beforeEach(context: ExtensionContext): Unit = exampleApi.resetAll() - override fun afterAll(context: ExtensionContext): Unit = exampleApi.stop() -} diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index ea3dd6f..2f16c67 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -8,10 +8,16 @@ management.endpoint: hmpps-auth: url: "http://localhost:8090/auth" -# example client configuration for calling out to other services -# TODO: Remove / replace this configuration -example-api: - url: "http://localhost:8091" - client: - id: "example-api-client" - secret: "example-api-client-secret" +spring: + datasource: + url: jdbc:postgresql://localhost:5432/health-and-medication-data + + main: + allow-bean-definition-overriding: true + + jpa: + show-sql: true + +database: + username: health-and-medication-data + password: health-and-medication-data \ No newline at end of file